BRTDfunc to model angular dependent transmission

Good evening,

I’m trying to use BRTDfunc together with the Roos (2001) model for the generation of a BSDF.

I have the following (measured) data:

  • normal-global reflectance (0.13)

  • normal-direct transmission (0.01)

  • normal-diffuse transmission (0.26)

  • angular dependence of normal-global transmission, from which I’ve found the parameters (p=0, q=3) to generate the Roos model polynomial.

The following code has been modified by that used by Wienold et al. (2017), and it works without errors (the cut-off angle is 90 deg):

void BRTDfunc LCG_off
10 0.0 0.0 0.0
0 0 0
9 0.13 0.13 0.13 0.13 0.13 0.13 0.26 0.26 0.26

However, I don’t want the “0 0 0”, since there IS a directional-diffuse component. I tried adding “tau(0.26, 0, 3, 90) tau(0.26, 0, 3, 90) tau(0.26, 0, 3, 90)” instead of “0 0 0”, however I get the error “tau(0.26, 0, 3, 90): undefined function”.

To summarise, I want to model both the direct-direct and direct-diffuse as being incidence angle dependent (based on the Roos model I created), however at the moment it seems I am only able to model the direct-direct component.

I’ve tried reading the Radiance documentation about BRTDfunc, but I’m somewhat confused the sentence “The functions rbrtd, gbrtd and bbrtd take the direction to the incident light (and its solid angle) and compute the color coefficients for the directional diffuse part of reflection and transmission.” The function I want to use is tau(tau0,p,q,cut) (Roos model), which I already have as a .cal file (although this doesn’t calculate anything about reflection).

Thanks a lot for your help!


p.s. the papers I mentioned are Redirecting and Annual glare evaluation for fabrics - the last link lets you download the .cal file I used.

Hi Joe,

The BRTDfunc type is probably the most confusing material primitive in the Radiance collection, which is really saying something…

Whereas the specular reflection and transmission coefficients are purely functions of the standard Radiance ray variables (e.g., Nx, Ny, Nz, Dx, Dy, Dz, etc.), the directional-diffuse component is computed from those variables and the direction and size of each light source they are applied to. Therefore, you only give the function names, which can be the same name repeated 3 times for RGB, and define it/tem in your file like so:

directional_diffuse(idx,idy,idz,iom) = {some function of incident direction (idx,idy,idz) and subtended solid angle iom};

Unfortunately, the resulting material calculation will not be complete in the sense that only light sources are considered for the directional-diffuse portion. If you only care about the light scattered by the sun or other known sources and can safely neglect the sky, ground, and any indirect contributions to the directional-diffuse portion, then you are OK. Otherwise, you might want to consider the more complete solution offered by the aBSDF material type, as shown below.

To use a BSDF or aBSDF primitive from a function, you create an appropriate .cal file and call either bsdf2klems or bsdf2ttree. A complete example using a modified Kotey model for roller shades with a Gaussian lobe is given below. Here is the “” file:

	Modified Kotey shade model with Gaussian lobe
	converted to BSDF that obeys reciprocity

	Need to specify:

	AccDeg	- peak full width angle for half maximum in degrees (constant)
	T0s	- transmittance at normal incidence (direct-direct)
	T0d	- transmittance at normal incidence (direct-diffuse)
	R0df	- front reflection at normal incidence (direct-diffuse)
	R0db	- back reflection at normal incidence (direct-diffuse)
bound(a,x,b) : if(a-x, a, if(x-b, b, x));
Exp(x) : if(-x-100, 0, exp(x));
Acos(x) : acos(bound(-1,x,1));
GaussFact : sqrt(-.5/log(.5));	{ ratio of Gaussian radius over half-max radius }
Gaussian(r) : .5/PI * Exp(-.5*r*r);
abs(x) : if(x, x, -x);
xor(a,b) : if(a, -b, b);
b(T0) : max(-.35*log(max(T0,.01)),.35);
			{ functions take argument of ct=cos(theta) }
Ts(ct) : T0s*ct^(b(T0s)-1);
Tdh(ct) : T0d*ct^b(T0d);

Ry(R0) : R0/(1-T0s);
R90d(R0) : R0 + (1-R0)*(.7*Ry(R0)^.7);
Rdfh(ct) : (R0df + (R90d(R0df) - R0df)*(1-ct^.6));
Rdbh(ct) : (R0db + (R90d(R0db) - R0db)*(1-ct^.6));

			{ make hemispherical diffuse into BSDFs }
Td(cti,cto) : T0d*(b(T0d)+2)/(2*PI) * cti^b(T0d) * cto^b(T0d);

Rdf(cti,cto) : Rdfh(cti) * Rdfh(cto) / (PI*R0df + 3*PI/13*(R90d(R0df)-R0df));
Rdb(cti,cto) : Rdbh(cti) * Rdbh(cto) / (PI*R0db + 3*PI/13*(R90d(R0db)-R0db));

			{ compute Gaussian function }
lobe2(a,k) : k*k * Gaussian(k*a);
lobe(ct) : lobe2(Acos(ct), 1/(GaussFact*PI/360* AccDeg ));

			{ complete model }
gaussKotey(ix,iy,iz,sx,sy,sz) : if(xor(iz,sz),
		max(Ts(abs(iz))*lobe(-ix*sx-iy*sy-iz*sz), Td(abs(iz),abs(sz))),
		if(iz, Rdf(iz,sz), Rdb(-iz,-sz)));

To use this definition to create an isotropic tensor tree, you can run something like:

bsdf2ttree +forward +backward -t3 -g 6 -f -e “AccDeg:2.5” -f gaussKotey > aerc6220_tt6.xml

Where the file “” contains reflectance/transmittance measurements required by “”:

{ Reflectance and Transmittance measured using AERC recommendation }
T0s : 0.0608;
T0d : 0.0926 - T0s;
R0df : 0.3862;
R0db : R0df;	{ not measured? }

The “AccDeg” constant set on the command-line is the acceptance angle in degrees for the specular portion, which gets represented in this case as a Gaussian lobe. The bottom line is that file defines a complete BSDF, which bsdf2ttree samples according to some maximum resolution, then adaptively reduces into an isotropic tensor tree that can be used in a BSDF or aBSDF type like so:

void aBSDF aerc6220
5 aerc6220_tt6.xml 0 0 1 .

(If your material is on a horizontal surface, you’ll need a different “up” vector, which doesn’t really matter in the case of an isotropic material.)

If you still want to use the BRTDfunc type and need a more complete example, I have one for the He-Torrance model that is even more complicated than what’s above.


1 Like

Dear Greg,

Thank you for your detailed reply! Reading your reply, and re-reading for the n-th time the papers I refered to, I wonder whether I’m approaching my problem in the correct way, so maybe I change my question to how I should best approach modelling my system. Perhaps BRTDfunc is unnecessarily complex for my setup.

My end goal is to generate a BSDF for use in three phase simulations. I am modelling liquid crystal glazing, which (particularly in the “off” state) has significant direct-diffuse transmission, and also has angular dependency for direct-hemisperical transmission.

Today I wondered whether I could somehow combine two Radiance material layers, each taking care of a different type of behaviour. For the dir-dir/dir-dif characteristics, I thought of using trans material. For the angle dependency, I thought of simply using glass with transmissivity =1 and a low refraction index to reduce the overall transmission for large angles. I guess I would need to calculate which refraction index best characterised the angular dependency I have data for.

With this method, I was able to generate two seperate BSDFs each with characteristics like I wanted, but I had trouble trying to combine them to make one BSDF (What is the preferred method for combining BSDF files? - Unmet Hours), the angular dependency stopped being monotonous (it sort of oscillates but eventually decreases).

As I said before, I have measurements for normal-hemispherical reflectance, normal-normal transmission, normal-diffuse transmission, and angular dependency of normal-hemispherical transmission.

In summary, my questions are:

  1. Does this sound like a good approach, and if so…
  2. Is there a better way of modelling the angular dependence rather than having to work out an effective refraction index and using a glass layer?
  3. How should I combine the two BSDFs in order to combine to two types of behaviour?
  4. Regarding the trans material, how should I estimate Rd and Rs if I only have Rhemispherical=Rd+Rs?
  5. Or, if this sounds like a terrible approach, can you suggest something better?

Hi Joe,

I should have thought of it earlier, as this is something I worked out with Jacob Jonsson @JCJonsson just recently. As luck would have it, there is a new script in the Radiance repertoire that takes tabulated isotropic measurements like those you describe and converts them into a Klems XML file suitable for use with the BSDF and aBSDF types (as well as matrix-based annual simulation methods). It takes as input a table of measurements in the following order:

th Ts Td Rs Rd
0 .07 .1 0 .15
25 .06 .11 0 .14
55 .05 .13 0 .12
80 .005 .08 0 .18
100 .005 .08 0 .25
125 .05 .13 0 .28
155 .06 .11 0 .32
180 .07 .1 0 .35

The header row is optional, but shows the expected data for each column. The first column should be theta, increasing values from 0 to 180°, followed by the corresponding specular transmission, diffuse hemispherical transmission, specular reflection, and diffuse hemispherical reflection. Diffuse hemispherical transmission plus specular transmission should add up to total hemispherical transmission, and the same holds for reflection. The new iso2klems script has the following usage:

Usage: iso2klems [-t][-f “x=string;y=string”][-u unit] [input.dat]

The theta spacing should be less than 10° or so, but you should not give a value at theta=90°. Spacing can be irregular.

The “-t” option reverses the notion of theta=0 from being incident on the “outside” face (towards exterior) to “inside” (towards interior). The other options follow those of wrapBSDF. The Klems XML file is produced on the standard output, which should be redirected to a file.

This will be by far simpler and more accurate than the other methods you’re looking at. It will probably also render more quickly.


Dear Greg,

You’re a life-saver, this works perfectly!

Many thanks and kind regards,

1 Like

Hi again Greg,

Is it possible to have the .xml file generated with iso2klems use commas as the delimiter in the matrices (i.e. the same as BSDF outputs created in Window)? My output uses “\t\n”.

I suspect this may be the reason I’m having trouble combining this layer with other layers in Window.

I’ve tried simple brute force with find and replace, but there are so many lines my computer just crashes.

Many thanks,

Hi @Greg_Ward, I’m tagging you in case the post is marked as solved and you didn’t see my last message.

I was just wondering whether it’s possible to change the delimiter used in the iso2klems output, to match WINDOW, i.e. “,” instead of “\t\n”. Otherwise multiplication with other BSDFs from WINDOW seems to give errors.

I managed to solve this with this line of code:
sed -z ‘s/\t\n/,/g’ file1.xml > file2.xml but I still had to manually change the last line of each of the four matrices.

Can you think of a better way? Or, if I’ve discovered a bug, could the choice of delimiter be an option in the iso2klems script.

Many thanks,

Hi Joe,

You’re right – I didn’t see you earlier post for some reason. I thought I got notifications on all posts I had replied to, but perhaps not.

What version of WINDOW are you using, and what errors are you seeing? Are you sure the issue is missing commas in the data? I ask because none of the Radiance programs that generate Klems XML files put commas in the data. According to the documentation I have, these are completely optional.

This might be something you need to take up with the WINDOW team if the problem is not on our end. I checked that my own routines are happy reading in the output from iso2klems.


Dear Greg,

I am trying to match the modified Kotey shade model with Gaussian lobe with the original Kotey’s model.

I am not sure whether they are the same in terms of the semi-empirical models.

For example, the beam-beam transmission model in the “gaussKoteyBSDF” looks different from Kotey’s original model.


Hi Sichen,

I am not sure I can provide much help on this, but perhaps @JCJonsson can offer advice, as he modified Kotey’s original model. The Gaussian version of it has a variable lobe size, whereas the standard translation to Klems just uses pairs of directions for the “specular” components. A wider distribution may “leak” into neighboring directions.

It would help to know what commands you are using, what you are seeing, and what you expected to see.


Hi Greg,

I am working on a workflow to provide recommendations for roller shades based on glare and daylight autonomy. I have been working with Honeybee-Radiance, but non-BSDF modifiers/materials cannot fully describe the angular properties of beam-beam and beam-diffuse transmission of roller shades. I learned the limitations of transdata and BRTDfunc, and luckily I found your post about gaussKoteyBSDF and iso2klems.

I am trying to find a way to model angular properties of roller shades as modifiers based on Kotey’s models (characterized by angular beam-beam transmission, beam-diffuse transmission, and reflection). It seems like that bi-directional transmission of roller shades in BSDF has always been the sum of direct and diffuse components. Jacob’s post makes me think that BSDF datasets have not separated diffuse and direct components for now, so Radiance does not separate the direct and diffuse calculations when a BSDF modifier is used.

(Data-driven BSDF or BSDF of real products won’t be an ideal solution in this case, since they are very specific to the products, and my work is to provide recommendations for roller shades in general. genBSDF might not be the best option for roller shades.)

Many thanks,

Hi Sichen,
The modification we did to Kotey after looking at measurements of a larger fabric dataset was simply changing an exponent from 0.5 to 0.35. I don’t have the equations in front of me but if you see the code for modified Kotey it should be easy to get back to the original.

Hi @JCJonsson,

Thank you for your prompt response. Indeed, after I changed 0.35 back to 0.5, the angular values looks the same as the orignal Kotey’s model. By any chance, do you have a documentation/paper about how you modify Kotey’s models? It would be nice to have a reference of the model in our conference paper.

I just replicated BSDF of a real roller shade by using the gaussKoteyBSDF, and visualized their klems in BSDF viewer, and their angular values look very similar.

Since you are from LBNL, I wonder whether you would know how xml files of roller shades (stored at C:\Users\Public\LBNL\WINDOW7.8\XML) are generated (from measurements, or analytical models?).

Many thanks,

Hi Sichen,

If you want to do a glare evaluation, in my point of view the core part to be modeled as correct as possible is the peak rather than the diffuse transmittance. Meaning I would rather sacrifice to model a “correct” angular diffuse transmittance than to sacrifice the accuracy of the angular beam transmittance since it is mainly the beam causing glare for such shadings. As Greg mentioned in the other post, it is super tricky to reproduce the real luminance distribution around the peak correctly, especially when using BSDF. (this is one of the reasons to have the peak extraction in aBSDF). For that reason (aBSDF was not available at that time) I used used BRTDfunc to calculate transmittance thresholds for fabrics in EN14501 and EN17037, described in “Annual glare evaluation for fabrics”. It uses an modified Roos model for the angular direct-direct transmittance and has the normal-normal transmittance and the cut-off-angle as input. I would guess the model works reasonably for typical fabrics used to control glare, which means with rather low (<10%) normal-diffuse transmittance (=dark colors).
The glare evaluation should then also include the blurring the peak to mimic the scattering in the eye (that happens similarly when you capture an HDR image). The importance of this (and how this can be done) is mentioned in Greg’s paper and in my presentation at the radiance workshop in Bilbao 2021.


1 Like

Hi @Jan_Wienold,

Thanks for the detailed reply. I have read and tried your code previously. Thank you for sharing the code, and it is clear and works on my end. Now I understand why you used BRTDfunc while sacrificing the beam-diffuse part.

Is that correct that aBSDF is recommended for peak extraction and glare evaluations if the BSDF dataset does not separate the direct and diffuse part?


Hi Sichen,
I think that AERC 1.1 is the technical document where it is published, I don’t think my original report ever got published in a quotable way.

The roller shade XML files in CGDB are generated from measured spectral data that is converted to XML files using modified Kotey to get the angle dependence and then a very crude model for splitting direct and diffuse components are used (documented in the WINDOW technical manual sect 12.4, Greg derived a better way to spread the diffuse component out so that reciprocity is conserved but that has not been implemented yet.

1 Like

Dear Jacob,

Thank you so much for the reference. I saw the bi-directional transmission as the sum of the angular direct and diffuse components in the WINDOW technical manual.

A more general question: when a BSDF modifier of roller shades is used, does Radiance calculate angular direct and diffuse transmission separately? (The question is answered here :slight_smile: : Grey’s post)


This is not an easy question to answer, since it depends both on the BTDF being used and whether you employ the Radiance “BSDF” or “aBSDF” material type. Both will account for “specular” separate from pure Lambertian transmission, but aBSDF will also handle the beam or view component as unscattered light, which has advantages for certain materials like roller shades.


1 Like

Dear Greg,

Thank you for your response, which cleared my major confusions.


Dear all,

For you information, I compared the direct-direct transmission of a roller shade calculated from Modified-Kotey model, Original-Kotey model, and Roos-Wienold model. The Modified-Kotey does not have a cut-off angle.

Eqn. 1: Direct-direct transmittance in the Modified-Kotey model is:

Ts(ct) : T0s*ct^(b(T0s)-1);

Eqn. 2: removing the “-1” term in Eqn 1.

Ts(ct) : T0s*ct^(b(T0s));

If you want to use the Original-Kotey model to generate BSDF datasets based on, you could replace/add the following code:

cutoff(T0): (65 + (95-65)*(1-cos(T0 * PI/2)))*PI/180;

B(T0): 0.6*(cos(T0*PI/2))^0.3;

Ts(ct) : if (ct-cos(cutoff(T0s)),T0s*cos( (Acos(abs(ct)))/cutoff(T0s)*PI/2)^B(T0s),0);