标签:
近段时间做一个关于水面的动画。由于我是在OpenFrameworks里面实现水面动画的(因为里面封装了我需要的opengl功能),然而使用of中的opengl来渲染水面却是很蛋疼的一件事情,或者至少说我不会,同学说我用of做的那只能叫可视化不叫渲染……
恰好进来在学习pbrt,所以索性就蛋疼了考虑直接用pbrt来渲染吧……(至于为什么,仅为好玩儿……)
pbrt默认的渲染方式是使用一个场景描述文件.pbrt,我要渲染的对象是三角网络就必须使用对应的描述语句定义三角对象:
Shape "trianglemesh"
"integer indices" [0 2 1 ]
"point P" [0 0 0.401925 2 0 0.92604 0 2 0.950944 ]
"normal N" [-0.245007 -0.256649 0.934935 -0.439362 -0.242124 0.865065 -0.228464 -0.466945 0.854264 ]
对应的参数都很清晰不多说了,我的方法就是将我的三角网络拆成三角形按照这个格式一个一个输出到文件,mesh.pbrt:
Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [0 0 0.401925 2 0 0.92604 0 2 0.950944 ] "normal N" [-0.245007 -0.256649 0.934935 -0.439362 -0.242124 0.865065 -0.228464 -0.466945 0.854264 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [2 0 0.92604 2 2 1.48582 0 2 0.950944 ] "normal N" [-0.439362 -0.242124 0.865065 -0.387612 -0.436964 0.811677 -0.228464 -0.466945 0.854264 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [2 0 0.92604 4 0 1.41771 2 2 1.48582 ] "normal N" [-0.439362 -0.242124 0.865065 -0.371551 -0.220212 0.901918 -0.387612 -0.436964 0.811677 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [4 0 1.41771 4 2 1.90603 2 2 1.48582 ] "normal N" [-0.371551 -0.220212 0.901918 -0.30709 -0.3961 0.865333 -0.387612 -0.436964 0.811677 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [4 0 1.41771 6 0 1.74995 4 2 1.90603 ] "normal N" [-0.371551 -0.220212 0.901918 -0.280111 -0.208777 0.93699 -0.30709 -0.3961 0.865333 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [6 0 1.74995 6 2 2.19558 4 2 1.90603 ] "normal N" [-0.280111 -0.208777 0.93699 -0.235475 -0.36421 0.901056 -0.30709 -0.3961 0.865333 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [6 0 1.74995 8 0 2.01561 6 2 2.19558 ] "normal N" [-0.280111 -0.208777 0.93699 -0.198564 -0.198247 0.959828 -0.235475 -0.36421 0.901056 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [8 0 2.01561 8 2 2.4287 6 2 2.19558 ] "normal N" [-0.198564 -0.198247 0.959828 -0.165144 -0.344725 0.924063 -0.235475 -0.36421 0.901056 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [8 0 2.01561 10 0 2.1637 8 2 2.4287 ] "normal N" [-0.198564 -0.198247 0.959828 -0.0981442 -0.190148 0.976837 -0.165144 -0.344725 0.924063 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [10 0 2.1637 10 2 2.55301 8 2 2.4287 ] "normal N" [-0.0981442 -0.190148 0.976837 -0.0739892 -0.338099 0.938198 -0.165144 -0.344725 0.924063 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [10 0 2.1637 12 0 2.21655 10 2 2.55301 ] "normal N" [-0.0981442 -0.190148 0.976837 -0.0228768 -0.181805 0.983068 -0.0739892 -0.338099 0.938198 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [12 0 2.21655 12 2 2.58642 10 2 2.55301 ] "normal N" [-0.0228768 -0.181805 0.983068 -0.00178289 -0.322288 0.94664 -0.0739892 -0.338099 0.938198 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [12 0 2.21655 14 0 2.21024 12 2 2.58642 ] "normal N" [-0.0228768 -0.181805 0.983068 -0.0283834 -0.170657 0.984922 -0.00178289 -0.322288 0.94664 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [14 0 2.21024 14 2 2.55678 12 2 2.58642 ] "normal N" [-0.0283834 -0.170657 0.984922 -0.0141646 -0.301278 0.953431 -0.00178289 -0.322288 0.94664 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [14 0 2.21024 16 0 2.27419 14 2 2.55678 ] "normal N" [-0.0283834 -0.170657 0.984922 -0.130533 -0.167088 0.977263 -0.0141646 -0.301278 0.953431 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [16 0 2.27419 16 2 2.61614 14 2 2.55678 ] "normal N" [-0.130533 -0.167088 0.977263 -0.1316 -0.283584 0.949875 -0.0141646 -0.301278 0.953431 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [16 0 2.27419 18 0 2.47738 16 2 2.61614 ] "normal N" [-0.130533 -0.167088 0.977263 -0.194556 -0.172126 0.965671 -0.1316 -0.283584 0.949875 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [18 0 2.47738 18 2 2.83387 16 2 2.61614 ] "normal N" [-0.194556 -0.172126 0.965671 -0.213386 -0.284985 0.934479 -0.1316 -0.283584 0.949875 ] Shape "trianglemesh" "integer indices" [0 2 1 ] "point P" [18 0 2.47738 20 0 2.67713 18 2 2.83387 ] "normal N" [-0.194556 -0.172126 0.965671 -0.20205 -0.190085 0.960751 -0.213386 -0.284985 0.934479 ]
......
按照pbrt给出的例子写出场景主文件:
#定义表面积分器 SurfaceIntegrator "directlighting" #transform ConcatTransform [ 0.828849 -0.295370 -0.475149 0.000000 -0.559473 -0.437585 -0.703924 0.000000 -0.000000 0.849280 -0.527943 0.000000 0.000000 0.000000 0.000000 1.000000 ] Translate -4.860000 -7.200000 -5.400000 #定义摄像机 Camera "perspective" "float fov" [90.00000 ] "float shutteropen" [0.000000 ] "float shutterclose" [0.000000 ] "float screenwindow" [-1.000000 1.000000 -1.000000 1.000000 ] "float frameaspectratio" [1.333333 ] #定义胶片 Film "image" "integer xresolution" [200 ] "integer yresolution" [200 ] "string filename" "1.exr" #定义采样器 Sampler "lowdiscrepancy" "integer pixelsamples" [8] PixelFilter "box" WorldBegin AttributeBegin LightSource "infinite" "string mapname" ["textures/skylight-dusk.exr"] "color L" [0.5 0.5 0.5] "integer nsamples" [16] AttributeEnd AttributeBegin #mesh的材质 Material "uber" "color Kd" [0 0.1 0.15] "color Kr" [0.9 0.9 0.9] "color Ks" [0.1 0.1 0.1] "float roughness" [0.9] "float index" [1.34] #mesh trnsform Translate -90 -80 -2 Scale 0.1 0.1 0.3 #包含mesh Include "mesh.pbrt" AttributeEnd WorldEnd
执行下列命令(当然你要先编译好pbrt,这个直接下pbrt-v2编译就好,没什么需要说明的地方):
pbrt scene.pbrt
执行pbrt就会出现一个进度条开始渲染对象,渲染完成后就会在当前目录输出一个1.exr图像。
但是我要做的是动画,每帧mesh有至少有N*N*2个三角面,N>=512,我的动画打算30秒,20帧,一共600张图,需要600个mesh,这个要是手动来一个一个搞,别说mesh输出一共要600*100M+,就是人也要累死啊。
考虑用写个批处理来搞一下感觉也不好使,后来反正因为学习pbrt就决定把pbrt的代码嵌入到of中去算了。
pbrt代码本身唯一的文档,就是pbrt那本书了,但是型号它的API很清晰易懂,几乎每个api都是场景描述文件对应的,所以我只需要把场景pbrt文件翻译成c++代码就可以调用pbrt了,pbrt的API如下:
void pbrtInit(const Options &opt); void pbrtCleanup(); void pbrtIdentity(); void pbrtTranslate(float dx, float dy, float dz); void pbrtRotate(float angle, float ax, float ay, float az); void pbrtScale(float sx, float sy, float sz); void pbrtLookAt(float ex, float ey, float ez, float lx, float ly, float lz, float ux, float uy, float uz); void pbrtConcatTransform(float transform[16]); void pbrtTransform(float transform[16]); void pbrtCoordinateSystem(const string &); void pbrtCoordSysTransform(const string &); void pbrtActiveTransformAll(); void pbrtActiveTransformEndTime(); void pbrtActiveTransformStartTime(); void pbrtTransformTimes(float start, float end); void pbrtPixelFilter(const string &name, const ParamSet ¶ms); void pbrtFilm(const string &type, const ParamSet ¶ms); void pbrtSampler(const string &name, const ParamSet ¶ms); void pbrtAccelerator(const string &name, const ParamSet ¶ms); void pbrtSurfaceIntegrator(const string &name, const ParamSet ¶ms); void pbrtVolumeIntegrator(const string &name, const ParamSet ¶ms); void pbrtRenderer(const string &name, const ParamSet ¶ms); void pbrtCamera(const string &, const ParamSet &cameraParams); void pbrtWorldBegin(); void pbrtAttributeBegin(); void pbrtAttributeEnd(); void pbrtTransformBegin(); void pbrtTransformEnd(); void pbrtTexture(const string &name, const string &type, const string &texname, const ParamSet ¶ms); void pbrtMaterial(const string &name, const ParamSet ¶ms); void pbrtMakeNamedMaterial(const string &name, const ParamSet ¶ms); void pbrtNamedMaterial(const string &name); void pbrtLightSource(const string &name, const ParamSet ¶ms); void pbrtAreaLightSource(const string &name, const ParamSet ¶ms); void pbrtShape(const string &name, const ParamSet ¶ms); void pbrtReverseOrientation(); void pbrtVolume(const string &name, const ParamSet ¶ms); void pbrtObjectBegin(const string &name); void pbrtObjectEnd(); void pbrtObjectInstance(const string &name); void pbrtWorldEnd();
对比一下场景描述文件,发现基本一一对应。
pbrt本身是被编译成lib静态库的,而pbrt.exe仅仅是一个引用pbrtlib的单文件c++程序而已:
#include "stdafx.h" #include "api.h" #include "probes.h" #include "parser.h" #include "parallel.h" // main program int main(int argc, char *argv[]) { Options options; vector<string> filenames; // Process command-line arguments for (int i = 1; i < argc; ++i) { if (!strcmp(argv[i], "--ncores")) options.nCores = atoi(argv[++i]); else if (!strcmp(argv[i], "--outfile")) options.imageFile = argv[++i]; else if (!strcmp(argv[i], "--quick")) options.quickRender = true; else if (!strcmp(argv[i], "--quiet")) options.quiet = true; else if (!strcmp(argv[i], "--verbose")) options.verbose = true; else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) { printf("usage: pbrt [--ncores n] [--outfile filename] [--quick] [--quiet] " "[--verbose] [--help] <filename.pbrt> ...\n"); return 0; } else filenames.push_back(argv[i]); } // Print welcome banner if (!options.quiet) { printf("pbrt version %s of %s at %s [Detected %d core(s)]\n", PBRT_VERSION, __DATE__, __TIME__, NumSystemCores()); printf("Copyright (c)1998-2014 Matt Pharr and Greg Humphreys.\n"); printf("The source code to pbrt (but *not* the book contents) is covered by the BSD License.\n"); printf("See the file LICENSE.txt for the conditions of the license.\n"); fflush(stdout); } pbrtInit(options); // Process scene description PBRT_STARTED_PARSING(); if (filenames.size() == 0) { // Parse scene from standard input ParseFile("-"); } else { // Parse scene from input files for (u_int i = 0; i < filenames.size(); i++) if (!ParseFile(filenames[i])) Error("Couldn‘t open scene file \"%s\"", filenames[i].c_str()); } pbrtCleanup(); return 0; }
其中起到解析文件作用就是如下这段:
pbrtInit(options); // Process scene description PBRT_STARTED_PARSING(); if (filenames.size() == 0) { // Parse scene from standard input ParseFile("-"); } else { // Parse scene from input files for (u_int i = 0; i < filenames.size(); i++) if (!ParseFile(filenames[i])) Error("Couldn‘t open scene file \"%s\"", filenames[i].c_str()); }
它是使用yacc解析的,细节我并不懂,我把这个文件换成如下:
#include "stdafx.h" #include "api.h" #include "probes.h" #include "parser.h" #include "parallel.h" #include "paramset.h" extern void InitParamSet(ParamSet &ps, SpectrumType); void create_a_tri( Point i1,Point i2,Point i3, Normal n1,Normal n2,Normal n3 ) { ParamSet params; //mesh参数 int s[3] = {0,2,1}; params.AddInt("indices",s,3); Point p[3] = {i1,i2,i3}; params.AddPoint("P",p,3); Normal n[3] = {n1,n2,n3}; params.AddNormal("N",n,3); pbrtShape("trianglemesh",params); } int main(int argc, char *argv[]) { Options opt; opt.imageFile = "1.exr"; opt.nCores = 4; opt.openWindow = false; opt.quickRender = false; pbrtInit(opt); ParamSet params; /* InitParamSet(params, SPECTRUM_REFLECTANCE); pbrtSurfaceIntegrator("directlighting",params); */ //初始化所有参数 //表面积分器参数 int maxdepth = 5; params.AddInt("maxdepth",&maxdepth,1); string strategy = "all"; params.AddString("strategy",&strategy,1); //相机参数 float fov = 90; params.AddFloat("fov",&fov,1); float shutteropen = 0.00f; params.AddFloat("shutteropen",&shutteropen,1); float shutterclose = 0.00f; params.AddFloat("shutterclose",&shutterclose,1); float screenwindow[4] = {-1.0,1.0,-1.0,1.0}; params.AddFloat("screenwindow",screenwindow,4); float ratio = 1.333333; params.AddFloat("frameaspectratio",&ratio,1); //胶片 int xresolution = 640; int yresolution = 480; string filename = "1.exr"; params.AddInt("xresolution",&xresolution,1); params.AddInt("yresolution",&yresolution,1); params.AddString("filename",&filename,1); //采样器 int pixelsample = 8; params.AddInt("pixelsamples",&pixelsample,1); //过滤器 float xwidth = 0.5; float ywidth = 0.5; params.AddFloat("xwith",&xwidth,1); params.AddFloat("ywith",&ywidth,1); //光照 float L[3] = {0.5,0.5,0.5}; int nsamples = 16; string mapname = "textures/skylight-dusk.exr"; params.AddRGBSpectrum("L",L,3); params.AddInt("nsamples",&nsamples,1); params.AddString("mapname",&mapname,1); //材质 float Kd[3] = {0,0.1,0.15}; float Kr[3] = {0.9,0.9,0.9}; float Ks[3] = {0.1,0.1,0.1}; float roughness = 0.9; float index = 1.34; params.AddRGBSpectrum("Kd",Kd,3); params.AddRGBSpectrum("Kr",Kr,3); params.AddRGBSpectrum("Ks",Ks,3); params.AddFloat("roughness",&roughness,1); params.AddFloat("index",&index,1); pbrtSurfaceIntegrator("directlighting",params); float ct[16] = {0.828849,-0.295370,-0.475149,0.000000,-0.559473,-0.437585,-0.703924,0.000000,-0.000000,0.849280,-0.527943,0.000000,0.000000,0.000000,0.000000,1.000000}; pbrtConcatTransform(ct); pbrtTranslate(-4.860000,-7.200000,-5.400000); pbrtCamera("perspective",params); pbrtFilm("image",params); pbrtSampler("lowdiscrepancy",params); pbrtPixelFilter("box",params); pbrtWorldBegin(); pbrtAttributeBegin(); pbrtLightSource("infinite",params); pbrtAttributeEnd(); pbrtAttributeBegin(); pbrtMaterial("uber",params); //pbrtTranslate(-90,-80,-2); //pbrtScale(0.1,0.1,0.4); pbrtTranslate(-1,-1,-9); pbrtScale(1,1,1); //mesh参数 int s[3] = {0,2,1}; params.AddInt("indices",s,3); Point p[3] = {Point(0,0,11.8604),Point(2,0,11.2012),Point(0,2,11.2039)}; params.AddPoint("P",p,3); Normal n[3] = {Normal(13.1851,13.1312,40),Normal(25.1371,13.4084,40),Normal(13.4624,25.8694,40)}; params.AddNormal("N",n,3); pbrtShape("trianglemesh",params); create_a_tri(Point(0,0,11.8604),Point(2,0,11.2012),Point(0,2,11.2039),Normal(13.1851,13.1312,40),Normal(25.1371,13.4084,40),Normal(13.4624,25.8694,40)); create_a_tri(Point(2,0,11.2012),Point(2,2,10.5308),Point(0,2,11.2039),Normal(25.1371,13.4084,40),Normal(21.2989,23.2563,40),Normal(13.4624,25.8694,40)); pbrtAttributeEnd(); pbrtWorldEnd(); pbrtCleanup(); return 0; }
很好,和pbrt文件渲染出来类似结果。
有了这些就可以把它嵌入到of中了,定义两个函数:
void create_a_tri( Point i1,Point i2,Point i3, Normal n1,Normal n2,Normal n3 ); int render(const char* file_name,ofFloatPixels** pxs,int N,float LL,float lamda,float nla);
第一个创建一个三角形,第二个就是使用c++创建pbrt场景渲染输出。实现一下:
#include "render.h" #include "core\stdafx.h" #include "core\api.h" #include "core\probes.h" #include "core\parser.h" #include "core\parallel.h" #include "core\paramset.h" void create_a_tri( Point i1,Point i2,Point i3, Normal n1,Normal n2,Normal n3 ) { ParamSet params; //mesh参数 int s[3] = {0,2,1}; params.AddInt("indices",s,3); Point p[3] = {i1,i2,i3}; params.AddPoint("P",p,3); Normal n[3] = {n1,n2,n3}; params.AddNormal("N",n,3); pbrtShape("trianglemesh",params); } int render(const char* file_name,ofFloatPixels** pxs,int N,float LL,float lamda,float nla) { printf("Generating Mesh...\n"); Options opt; opt.nCores = 4; opt.openWindow = false; opt.quickRender = false; pbrtInit(opt); ParamSet params; //初始化所有参数 //表面积分器参数 int maxdepth = 5; params.AddInt("maxdepth",&maxdepth,1); string strategy = "all"; params.AddString("strategy",&strategy,1); //相机参数 float fov = 120; params.AddFloat("fov",&fov,1); float shutteropen = 0.00f; params.AddFloat("shutteropen",&shutteropen,1); float shutterclose = 0.00f; params.AddFloat("shutterclose",&shutterclose,1); float screenwindow[4] = {-1.0,1.0,-1.0,1.0}; params.AddFloat("screenwindow",screenwindow,4); float ratio = 1.333333; params.AddFloat("frameaspectratio",&ratio,1); //胶片 int xresolution = 200; int yresolution = 200; string filename = file_name; params.AddInt("xresolution",&xresolution,1); params.AddInt("yresolution",&yresolution,1); params.AddString("filename",&filename,1); //采样器 int pixelsample = 4; params.AddInt("pixelsamples",&pixelsample,1); //过滤器 float xwidth = 0.5; float ywidth = 0.5; params.AddFloat("xwith",&xwidth,1); params.AddFloat("ywith",&ywidth,1); //光照 float L[3] = {0.5,0.5,0.5}; int nsamples = 4; string mapname = "textures/skylight-dusk.exr"; params.AddRGBSpectrum("L",L,3); params.AddInt("nsamples",&nsamples,1); params.AddString("mapname",&mapname,1); //材质 float Kd[3] = {0,0.1,0.15}; float Kr[3] = {0.9,0.9,0.9}; float Ks[3] = {0.1,0.1,0.1}; float roughness = 0.9; float index = 1.34; params.AddRGBSpectrum("Kd",Kd,3); params.AddRGBSpectrum("Kr",Kr,3); params.AddRGBSpectrum("Ks",Ks,3); params.AddFloat("roughness",&roughness,1); params.AddFloat("index",&index,1); pbrtSurfaceIntegrator("directlighting",params); float ct[16] = {0.828849,-0.295370,-0.475149,0.000000,-0.559473,-0.437585,-0.703924,0.000000,-0.000000,0.849280,-0.527943,0.000000,0.000000,0.000000,0.000000,1.000000}; pbrtConcatTransform(ct); pbrtTranslate(-4.860000,-7.200000,-5.400000); pbrtCamera("perspective",params); pbrtFilm("image",params); pbrtSampler("lowdiscrepancy",params); pbrtPixelFilter("box",params); pbrtWorldBegin(); pbrtAttributeBegin(); pbrtLightSource("infinite",params); pbrtAttributeEnd(); pbrtAttributeBegin(); pbrtMaterial("uber",params); pbrtTranslate(-80,-80,-3); pbrtScale(0.1,0.1,0.1);
for(int i=0;i<N-1;i++) { for(int j=0;j<N-1;j++) { create_a_tri( Point((j+lamda*pxs[0]->getColor(j,i).r)*LL/N,(i+lamda* pxs[1]->getColor(j,i).r)*LL/N,pxs[2]->getColor(j,i).r*N/2), Point((j+1+lamda*pxs[0]->getColor(j+1,i).r)*LL/N,(i+lamda* pxs[1]->getColor(j+1,i).r)*LL/N,pxs[2]->getColor(j+1,i).r*N/2), Point((j+lamda*pxs[0]->getColor(j,i+1).r)*LL/N,(i+1+lamda* pxs[1]->getColor(j,i+1).r)*LL/N,pxs[2]->getColor(j,i+1).r*N/2), Normal(nla*pxs[3]->getColor(j,i).r,nla*pxs[3]->getColor(j,i).g,nla*pxs[3]->getColor(j,i).b), Normal(nla*pxs[3]->getColor(j+1,i).r,nla*pxs[3]->getColor(j+1,i).g,nla*pxs[3]->getColor(j+1,i).b), Normal(nla*pxs[3]->getColor(j,i+1).r,nla*pxs[3]->getColor(j,i+1).g,nla*pxs[3]->getColor(j,i+1).b) ); create_a_tri( Point((j+1+lamda*pxs[0]->getColor(j+1,i+1).r)*LL/N,(i+1+lamda* pxs[1]->getColor(j+1,1+i).r)*LL/N,pxs[2]->getColor(j+1,i+1).r*N/2), Point((j+1+lamda*pxs[0]->getColor(j+1,i).r)*LL/N,(i+lamda* pxs[1]->getColor(j+1,i).r)*LL/N,pxs[2]->getColor(j+1,i).r*N/2), Point((j+lamda*pxs[0]->getColor(j,i+1).r)*LL/N,(i+1+lamda* pxs[1]->getColor(j,i+1).r)*LL/N,pxs[2]->getColor(j,i+1).r*N/2), Normal(nla*pxs[3]->getColor(j+1,i+1).r,nla*pxs[3]->getColor(j+1,i+1).g,nla*pxs[3]->getColor(j+1,i+1).b), Normal(nla*pxs[3]->getColor(j+1,i).r,nla*pxs[3]->getColor(j+1,i).g,nla*pxs[3]->getColor(j+1,i).b), Normal(nla*pxs[3]->getColor(j,i+1).r,nla*pxs[3]->getColor(j,i+1).g,nla*pxs[3]->getColor(j,i+1).b) ); } } pbrtAttributeEnd(); pbrtWorldEnd(); pbrtCleanup(); return 0; }
虽然这样效率比较低,但是至少可以脱手了。
每帧我们计算完动画,自动调用render函数,就可以在无人看守下出图了。
最后要说的是,关于pbrt中关于图形学的内容就不细说了,东西都写在pbrt里面,uber材质是一种综合材质。
放2张图结尾吧:
标签:
原文地址:http://www.cnblogs.com/wubugui/p/4531181.html