This part of the project can be done by directly implement the BRDF function in  and :(eq1)
Although  provide two f()'s for both isotropic and anisotropic surfaces, we can still implement the Ward model with the one shown above.
Firstly, I refer to other material plug-in's to set up a material WardModel, then I create a BxDF class called Ward in the same file. The WardModel meterial contains only one BxDF, which is the Ward BxDF. The rest of the implementation is simply the follows (eq1) to construct the f() function of the Ward BxDF. My result is shown below. The results have rough surfaces with specular regions larger and brighter than the one shown on the course webpage. I thought it is because I had not implement the importance sampling, therefore I also implement the importance sampling, the bonus part.
The implementation is quite straightforward, too. I follow the procedure described in the section 2.1 of  and implement the Sample_f() function and Pdf() function according to (eq2) and (eq3).(eq2) (eq3)
However, it does not improved much after I applied importance sampling. The surfaces are smotther and the bottom of the spheres are darker, which is closer to the real world. But the specular regions have only shrinked a little and there exist many dots around the spheres.
Next, I tried to seperate the diffuse term and the specular term of (eq1) into two BxDf. Therefore, the WardModel Material now contains two BRDF's: a Lambertian for the diffuse term, and a Ward for the specular term. The result of this modification turns out to eliminate the dots of the result applying importance sampling but mess up the one without importance sampling. In addition, the specular regions become larger. Orz
The parameters of my results:resolution: 684 * 513
# samples per pixel: 1024
path tracing depth: 3
left: kd=(0.2, 0.2, 0.3), ks=(2.0, 2.0, 2.0), alpha_x=0.2, alpha_y=0.2
right kd=(0.3, 0.3, 0.0), ks=(1.4, 1.4, 1.4), alpha_x=0.5, alpha_y=0.1
Without importance sampling:
With importance sampling:
Without importance sampling + Seperate diffuse and specular:
With importance sampling + Seperate diffuse and specular:
For this part of the project, the implementation is pretty simple. First I follow the identical procedure as in part 1 and set up a plug-in meterial Tabulated which contains only one BRDF, TabulatedBxDF. Then read in the two binary files of the BRDF data by using the given API, read_brdf(). Finally, I calculate the theta and phi of both incident and outgoing vector, then get the corresponding value by using another given API, lookup_brdf_val().
The critical point of this part lies in calculating the phi's. Since arcsinf() returns angle between -2π and 2π, I have to shift the phi calculated by arcsinf() to the right quadrant according to the x and y fo the vector in order to get the desired phi ranges from 0 to 2π.
resolution: 684 * 513
# samples per pixel: 1024
path tracing depth: 2
To enable the bonus part (importance sampling), complie the source code with "#define IMPORTANCE_SAMPLING". To seperate the diffuse term and the specular term, complie the source code without "#define DIFFUSE_PLUS_SPECULAR".Source code: ward.cpp (Put it under $PBRT_ROOT/materials)
I assume the source code is placed under the same directory with BRDFRead.h, which is simply a file modified from the given BRDFRead.cpp by removing its main function.Source code: tabulated.cpp (Put it under $PBRT_ROOT/materials)