Open Shading Language from Scratch (Ubuntu)

Dans la série « from Scratch », nous allons voir cette fois-ci comment construire le language  « Open Shading Language », dit OSL, qui sert notamment à créer des « shaders » dans les programmes 3D.

Un « shader » (ou nuancier en français) permet de paramétrer une partie du processus de rendu réalisé par le moteur d’un logiciel d’image de synthèse.  OSL a été utilisé dans la réalisation du film « Men in Black 3 » par exemple.

Mais assez parlé, rentrons dans le vif du sujet…

OSLLe language Open Shading Language nécessite un certain nombre de programmes avant de pouvoir se construire. Voici la liste des joyeusetés identifiées :

  • ZLib
  • IlmBase, OpenEXR
  • libJPEG, libPNG, libTIFF, LibRAW (optionnel), OpenJpeg (optionnel)
  • Boost
  • Python
  • OpenImageIO
  • LLVM & CLang
  • PugiXML
  • Flex & GNU Bison

On utilisera le système Ubuntu pour construire tout cela. L’idée est de construire une par une chacune des dépendances (d’où l’article « from Scratch »), pour au final compiler OSL.

Toutes les sources seront stockées dans $HOME/Documents/C :

cd $HOME
mkdir Documents
cd Documents
mkdir C
cd C

 

ZLib

Etant donné qu’il s’agit de la dépendance la plus fine, nous commencerons par la bibliothèque ZLib.

wget https://zlib.net/zlib-1.2.11.tar.gz
tar -xvf zlib-1.2.11.tar.gz
rm zlib-1.2.11.tar.gz 
cd zlib-1.2.11/
./configure 
make -j4
sudo make install
cd ..

L’option « -j4 » de make permettra d’accélérer la construction des différentes briques logicielles en utilisant plusieurs processus pour compiler l’ensemble.

openexrIlmBase et OpenEXR

# IlmBase
wget https://github.com/openexr/openexr/releases/download/v2.3.0/ilmbase-2.3.0.tar.gz
tar -xvf ilmbase-2.3.0.tar.gz
rm ilmbase-2.3.0.tar.gz
cd ilmbase-2.3.0
./configure
make -j4
sudo make install
cd ..
# OpenEXR
wget https://github.com/openexr/openexr/releases/download/v2.3.0/openexr-2.3.0.tar.gz
tar -xvf openexr-2.3.0.tar.gz
rm openexr-2.3.0.tar.gz
cd openexr-2.3.0
./configure
make -j4
sudo make install
cd ..

L’installation de cette partie permettra de gérer le format d’image EXR nécessaire à certaines parties de la bibliothèque OSL.

 

Boostboost

Ici on passera par le script bootstrap.sh qui s’occupera de la première configuration :

wget https://dl.bintray.com/boostorg/release/1.67.0/source/boost_1_67_0.tar.gz
tar -xvf boost_1_67_0.tar.gz
rm boost_1_67_0.tar.gz 
cd boost_1_67_0
./bootstrap.sh
sudo ./b2 install
cd ..

 

libJPEG, libTIFF, libPNG, libRAW, openJpeg

Cette partie contient différentes bibliothèques graphiques, dont les deux dernières sont optionnelles :

# libjpeg
wget http://www.ijg.org/files/jpegsrc.v9c.tar.gz
tar -xvf jpegsrc.v9c.tar.gz
rm jpegsrc.v9c.tar.gz
cd jpeg-9c
./configure
make -j4
sudo make install
cd ..
# libtiff
wget http://download.osgeo.org/libtiff/tiff-4.0.10.tar.gz
tar -xvf tiff-4.0.10.tar.gz
rm tiff-4.0.10.tar.gz
cd tiff-4.0.10
./configure
make -j4
sudo make install
cd ..
#libpng
wget http://prdownloads.sourceforge.net/libpng/libpng-1.6.36.tar.gz?download
mv 'libpng-1.6.36.tar.gz?download' libpng-1.6.36.tar.gz
tar -xvf libpng-1.6.36.tar.gz 
rm libpng-1.6.36.tar.gz 
cd libpng-1.6.36/
./configure
make -j4
cd ..
sudo make install
# libraw
wget https://www.libraw.org/data/LibRaw-0.19.1.tar.gz
tar -xvf LibRaw-0.19.1.tar.gz 
rm LibRaw-0.19.1.tar.gz 
cd LibRaw-0.19.1
./configure
make -j4
sudo make install
cd ..
# openjpeg
wget https://github.com/uclouvain/openjpeg/archive/v2.3.0.tar.gz
tar -xvf v2.3.0.tar.gz
rm v2.3.0.tar.gz
cd openjpeg-2.3.0
mkdir build
cd build
cmake ..
make -j4
sudo make install
cd ..

 

python3Python

La suite se passe avec l’installation du language Python. Notez que la bibliothèque libFFI est requise également :

wget ftp://sourceware.org/pub/libffi/libffi-3.2.1.tar.gz
tar -xvf libffi-3.2.1.tar.gz 
rm libffi-3.2.1.tar.gz
cd libffi-3.2.1
./configure
make -j4
sudo make install
wget https://www.python.org/ftp/python/3.7.1/Python-3.7.1.tgz
tar -xvf Python-3.7.1.tgz 
rm Python-3.7.1.tgz 
cd Python-3.7.1
export CFLAGS="-Wno-error"
export CXXFLAGS="-Wno-error"
./configure --enable-optimizations --enable-shared
make -j4
sudo make install
cd ..

On a indiqué les options « -Wno-error » et « –enable-shared » pour ignorer les messages de warning et pour construire des bibliothèques partagées (shared libraries, ie « .so »).

 

OIIOOpenImageIO

Enfin, on arrive à la partie OpenImageIO qui va nécessiter d’indiquer où se trouve le programme python3 qui a été construit précédemment :

wget https://github.com/OpenImageIO/oiio/archive/Release-2.0.3.tar.gz
tar -xvf Release-2.0.3.tar.gz 
rm Release-2.0.3.tar.gz 
cd oiio-Release-2.0.3/
mkdir build
cd build
export LD_LIBRARY_PATH=/usr/local/lib
export PYTHON_EXECUTABLE=/usr/local/bin/python3
cmake .. -DPYTHON_EXECUTABLE=/usr/local/bin/python3
make -j4
sudo make install
cd ..

 

LLVMLLVM et CLang

Attention cette partie est la plus ardue : j’ai volontairement enlevé l’option « -j4 » de make car la compilation plantait régulièrement durant mes tests sous VirtualBox. Il vous faudra être patient car cela dure plusieurs heures.

CLang sera copié dans le répertoire de LLVM et le répertoire « build » doit se trouver en dehors de l’arborescence de LLVM et CLang.

wget http://releases.llvm.org/7.0.0/llvm-7.0.0.src.tar.xz
tar -xvf llvm-7.0.0.src.tar.xz
rm llvm-7.0.0.src.tar.xz
cd llvm-7.0.0.src
cd tools
wget http://releases.llvm.org/7.0.0/cfe-7.0.0.src.tar.xz
tar -xvf cfe-7.0.0.src.tar.xz
rm cfe-7.0.0.src.tar.xz 
cd cfe-7.0.0.src
cd ../../..
#in-tree build is not supported, so build directory is at the same level than llvm-7.0.0
mkdir build
cd build
export LD_LIBRARY_PATH=/usr/local/lib
export PYTHON_EXECUTABLE=/usr/local/bin/python3
export CFLAGS="-I/usr/local/include -L/usr/local/lib"
export CXXFLAGS="-I/usr/local/include -L/usr/local/lib"
cmake -DCMAKE_BUILD_TYPE=Release \
-DLLVM_BUILD_LLVM_DYLIB=ON \
-DLLVM_LINK_LLVM_DYLIB=ON \
-G "Unix Makefiles" ../llvm-7.0.0.src
make
sudo make install
cd ..

Deux options importantes à noter ici :

  • Release : pour éviter de construire CLang et LLVM en mode Debug qui serait encore plus long à réaliser
  • LLVM_BUILD_LLVM_DYLIB : pour construire la bibliothèque finale « libLLVM » dynamique afin d’obtenir un fichier « .so » global.

Si vous avez été patient, cela devrait payer : on arrive à la dernière ligne droite:)

bisonPugiXML, Flex et Bison

La suite qui sera plus rapide cette fois :

# PugiXML
wget http://github.com/zeux/pugixml/releases/download/v1.9/pugixml-1.9.tar.gz
tar -xvf pugixml-1.9.tar.gz
rm pugixml-1.9.tar.gz
cd pugixml-1.9
mkdir build
cd build
cmake -DBUILD_SHARED_LIBS=ON .. 
make
sudo make install
cd ../..
# Bison
wget http://ftp.gnu.org/gnu/bison/bison-3.0.tar.xz
tar -xvf bison-3.0.tar.xz
rm bison-3.0.tar.xz
cd bison-3.0
./configure
make 
sudo make install
cd ..
# Flex : use 2.6.3 because of 2.6.4 not building correctly
sudo apt-get install m4
wget https://github.com/westes/flex/releases/download/v2.6.3/flex-2.6.3.tar.gz
tar -xvf flex-2.6.3.tar.gz
rm flex-2.6.3.tar.gz
cd flex-2.6.3
./configure
make
sudo make install
cd ..

 

OSLsony

La partie OSL peut enfin être construite ! Notez les options pour ignorer les messages de warning.

Si jamais durant le make, le programme « python » n’est pas trouvé, vous pouvez toujours faire un lien symbolique de « /usr/bin/python » vers « /usr/bin/python3 »

# OSL
git clone https://github.com/imageworks/OpenShadingLanguage.git osl
cd osl
export CFLAGS="-Wno-error -Wno-maybe-uninitialized"
export CXXFLAGS="-Wno-error -Wno-maybe-uninitialized"
make

Il n’y a pas de phase d’installation et les fichiers générés se trouvent dans le répertoire « dist/linux64 ».

Si vous voulez vérifier que tout est ok, vous pouvez lancer la batterie de test :

make test

Maintenant que OSL a été complètement construit, allez dans le répertoire correspondant :

cd dist/linux64

Et voici ce qui est à votre disposition :

bin/oslc
bin/oslinfo
bin/testrender

Notez le détail de chaque programme :

  • oslc : il s’agit du compilateur OSL qui convertira les fichiers shaders « osl » en « oso »
  • oslinfo : ce programme permet de connaître les paramètres d’entrée/sortie d’un shader « oso »
  • testrender : un renderer basique qui reconnait uniquement les sphères mais qui permet de faire des tests rapides avec vos shaders

Enfin installez EXR-viewers qui servira pour la suite :

sudo apt-get install openexr-viewers

 

Shaders sur CornellBox

Prenons un des exemples de shader fourni dans la partie « testsuite » :

export OSLDIR=$HOME/Documents/C/osl
cd $OSLDIR/testsuite/render-cornell

Compilez le shader « metal » :

$OSLDIR/dist/linux64/bin/oslc metal.osl 
Compiled metal.osl -> metal.oso

Le fichier « osl » est bien compilé en « oso ».

Faites de même pour les deux suivants :

$OSLDIR/dist/linux64/bin/oslc matte.osl
$OSLDIR/dist/linux64/bin/oslc emitter.osl

Enfin lancez le rendu de la scene d’exemple :

$OSLDIR/dist/linux64/bin/testrender -aa 16 cornell.xml cornell.exr

Le fichier de la scène générée est au format EXR. Pour le visualiser lancez la commande :

exrdisplay cornell.exr

Voici le rendu obtenu. Notez la sphère métallique :

cornell

Le détail du shader « metal » joue notamment sur le paramètre de réflexion. En voici le contenu pour information :

surface metal
[[ string description = "Lambertian diffuse material" ]]
(
  float Ks = 1
  [[ string description = "Specular scaling",
    float UImin = 0, float UIsoftmax = 1 ]],
  float eta = 10
  [[ string description = "Metal's index of refraction (controls fresnel effect)",
    float UImin = 1, float UIsoftmax = 100 ]],
  color Cs = 1
  [[ string description = "Base color",
    float UImin = 0, float UImax = 1 ]]
)
{
  Ci = Ks * Cs * reflection (N, eta);
}

En utilisant le programme « oslinfo », on peut avoir un résumé des paramètres de ce shader :

$OSLDIR/dist/linux64/bin/oslinfo metal.oso
surface "metal"
float Ks 1
float eta 10
color Cs [ 1 1 1 ]

Trois paramètres d’entrée sont ici reconnus: Ks, eta et Cs. Le paramètre de sortie étant « Ci » directement indiqué dans le détail du shader (de type « color »).

 

Shader sur Microfacet

Voici un autre exemple de shader à titre d’illustration :

cd $OSLDIR/testsuite/render-microfacet
$OSLDIR/dist/linux64/bin/oslc checkerboard.osl 
$OSLDIR/dist/linux64/bin/oslc envmap.osl 
$OSLDIR/dist/linux64/bin/oslc glossy_glass.osl
$OSLDIR/dist/linux64/bin/oslc matte.osl

Le rendu de la scène :

$OSLDIR/dist/linux64/bin/testrender -aa 16 scene.xml scene.exr

Et enfin la visualisation :

exrdisplay scene.exr

Avec le résultat suivant :

microfacet.png

Notez les points parasites qui peuvent apparaitre à l’image. Ceci peut être amélioré en augmentant le nombre de rayons générés par pixel (paramètre -aa)

 

Shader OSL sous Blender

OSL a été adopté par un certain nombre de logiciels 3D dont Blender.

Voici l’illustration d’un shader qui donne l’apparence de la matière « or » sur un objet :

shader node_metal_gold(
color ForeColor = 0.8,
color MiddleColor = 0.4,
color BackColor = 0.1,
vector Normal = N,
float ForeRoughness = 0.01,
float BackRoughness = 0.04,
output closure color Metal = 0.0)
{
  closure color ForeClosure = background();
  closure color BackClosure = background();

  // Facing colors - formula from Blender OSL code
  float Factor1 = 1.0 - pow(fabs(dot(I,Normal)),2.0*0.465);

  // Dot vectors
  float Factor2 = clamp(pow(dot(Normal,I),1.3),0.0,1.0);

  ForeClosure = ((1-Factor1)*ForeColor + MiddleColor*Factor1)*microfacet_beckmann(Normal,ForeRoughness);
  BackClosure = BackColor*microfacet_beckmann(Normal,BackRoughness);

  Metal = ((1-Factor2)*BackClosure)+(ForeClosure*Factor2);
}

A noter: la fonction microfacet_beckmann() n’est pas reconnue par OSL en standard (seul microfacet() semble référencé), il s’agit sans doute d’ajout de la part de l’équipe de Blender.

Et voici comment cela se concrétise dans Blender 3D :

shader_blender

Pour ceux qui veulent tester, j’ai mis à disposition le fichier blender qui permet de construire cette scène avec cette texture : bmps.blend

L’image affichée à la une pour cet article correspond à un shader qui simule la saleté… Avouez que le résultat est plutôt probant 🙂 Et que cela montre la puissance du concept.

 

Shader OSL sous Renderman

Si vous avez suivi mon article précédent sous Renderman, vous avez peut-être installé Renderman sous Ubuntu.

De mon côté, j’en ai profité pour l’installer aussi sur MacOSX sur MacBookPro, et voici un exemple de shader disponible :

export RMANTREE=/Applications/Pixar/RenderManProServer-22.3
export PATH=$RMANTREE/bin:$PATH
cd $RMANTREE/lib/scenes/pattern/osl/
prman -d it -t:-2 oslball.rib

 

shader_renderman

Le shader « oak » correspondant à la partie extérieure de cette boule est assez touffu. Je vous laisse en juger :

// for the original renderman shader, check http://www.larrygritz.com/arman/materials.html 
// adapted from larry gritz advanced renderman patterns.h 
// Credit: Michel @ Small Blender Things

float smoothpulse (float e0, float e1, float e2, float e3, float x) 
{ 
return smoothstep(e0,e1,x) - smoothstep(e2,e3,x); 
} 

/* A pulse train of smoothsteps: a signal that repeats with a given 
* period, and is 0 when 0 <= mod(x/period,1) < edge, and 1 when 
* mod(x/period,1) > edge. 
*/ 
float smoothpulsetrain (float e0, float e1, float e2, float e3, float period, float x) 
{ 
return smoothpulse (e0, e1, e2, e3, mod(x,period)); 
} 

// adapted from larry gritz advanced renderman noises.h 
/* fractional Brownian motion 
* Inputs: 
* p position 
* octaves max # of octaves to calculate 
* lacunarity frequency spacing between successive octaves 
* gain scaling factor between successive octaves 
*/ 

/* A vector-valued antialiased fBm. */ 
vector vfBm (point p, float octaves, float lacunarity, float gain) 
{ 
float amp = 1; 
point pp = p; 
vector sum = 0; 
float i; 

for (i = 0; i < octaves; i += 1) { 
vector d = snoise(pp); 
sum += amp * d; 
amp *= gain; 
pp *= lacunarity; 
} 
return sum; 
} 

// adapted from larry gritz oak.sl and oak.h 
// original comments between /* ... */ 
// my comments start with // 
// note that I dropped the whole filterwidth stuff, partly 
// because I don't think it necessary in Blender Cycles, partly 
// because the derivatives and area() function doesn't seem to work (yet) 
// all specialized snoise defines are replaced by snoise() function calls 
float oaktexture (point Pshad, 
float dPshad, 
float ringfreq, 
float ringunevenness, 
float grainfreq, 
float ringnoise, 
float ringnoisefreq, 
float trunkwobble, 
float trunkwobblefreq, 
float angularwobble, 
float angularwobblefreq, 
float ringy, 
float grainy) 
{ 
/* We shade based on Pshad, but we add several layers of warping: */ 
/* Some general warping of the domain */ 
vector offset = vfBm(Pshad*ringnoisefreq, 2, 4, 0.5); 

point Pring = Pshad + ringnoise*offset; 
/* The trunk isn't totally steady xy as you go up in z */ 
vector d = snoise(Pshad[2]*trunkwobblefreq) ; 
Pring += trunkwobble * d * vector(1,1,0); 

/* Calculate the radius from the center. */ 
float r = hypot(Pring[0], Pring[1]) * ringfreq; 
/* Add some noise around the trunk */ 
r += angularwobble * smoothstep(0,5,r) 
* snoise (angularwobblefreq*(Pring)*vector(1,1,0.1)); 

/* Now add some noise so all rings are not equal width */ 
r += ringunevenness*snoise(r); 

float inring = smoothpulsetrain (.1, .55, .7, .95, 1, r); 

point Pgrain = Pshad*grainfreq*vector(1,1,.05); 
float dPgrain = dPshad; //dropped filterwidthp(Pgrain); 
float grain = 0; 
float i, amp=1; 
for (i = 0; i < 2; i += 1) { 
float grain1valid = 1-smoothstep(.2,.6,dPgrain); 
if (grain1valid > 0) { 
float g = grain1valid * snoise (Pgrain); 
g *= (0.3 + 0.7*inring); 
g = pow(clamp(0.8 - (g),0,1),2); 
g = grainy * smoothstep (0.5, 1, g); 
if (i == 0) 
inring *= (1-0.4*grain1valid); 
grain = max (grain, g); 
} 
Pgrain *= 2; 
dPgrain *= 2; 
amp *= 0.5; 
} 

return mix (inring*ringy, 1, grain); 
} 

// larry gritz' original shader was a closure but this shader 
// provides different outputs that you can plug into your own 
// closures/shaders 
surface oak( 
point Pos = P, 
float Sharpness = 0.01, // sharpness of the grain. hand tweaked because we lack derivatives. 
float ringfreq = 8, 
float ringunevenness = 0.5, 
float ringnoise = 0.02, 
float ringnoisefreq = 1, 
float grainfreq = 25, 
float trunkwobble = 0.15, 
float trunkwobblefreq = 0.025, 
float angularwobble = 1, 
float angularwobblefreq = 1.5, 
color Clightwood = color(.5, .2, .067), 
color Cdarkwood = color(0.15, 0.077, 0.028), 
float ringy = 1, 
float grainy = 1, 
output color Color = 0, 
output float Spec = 0.1, 
output float Roughness = 0.1, 
output float Disp = 0 
) 
{ 
point Pshad = transform ("object", Pos);
float wood = oaktexture (Pshad, Sharpness, ringfreq, ringunevenness, grainfreq,
ringnoise, ringnoisefreq, trunkwobble, trunkwobblefreq, 
angularwobble, angularwobblefreq, ringy, grainy); 

Color = mix (Clightwood, Cdarkwood, wood); 
Disp = -wood; // lightwood = 0, darkwood is deeper/lower = -1 
Spec = 0.1*(1-0.5*wood); // darkwood is less specular 
Roughness = 0.1+0.1*wood; // and rougher 
}

 

On voit bien ici que les shaders peuvent décrire des formules de texture assez dense, mettrent en place différentes fonctions et finalement seule l’imagination semble en être la limite.

Voici le fichier obtenu dans sa version plus large :

oslball.jpg

 

Pour aller plus loin

Pour ceux qui seraient intéressés, voici une liste de liens qui permettent de découvrir plus en détail le concept et le monde des shaders avec OSL :

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion /  Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s