0030700: Visualization, TKOpenGl - support PBR Metallic-Roughness shading model
[occt.git] / src / OpenGl / OpenGl_PBREnvironment.cxx
1 // Author: Ilya Khramov
2 // Copyright (c) 2019 OPEN CASCADE SAS
3 //
4 // This file is part of Open CASCADE Technology software library.
5 //
6 // This library is free software; you can redistribute it and/or modify it under
7 // the terms of the GNU Lesser General Public License version 2.1 as published
8 // by the Free Software Foundation, with special exception defined in the file
9 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
10 // distribution for complete text of the license and disclaimer of any warranty.
11 //
12 // Alternatively, this file may be used under the terms of Open CASCADE
13 // commercial license or contractual agreement.
14
15 #include <OpenGl_PBREnvironment.hxx>
16
17 #include <Graphic3d_PBRMaterial.hxx>
18 #include <OpenGl_ArbFBO.hxx>
19 #include <OpenGl_FrameBuffer.hxx>
20 #include <OpenGl_ShaderManager.hxx>
21 #include <OSD_Timer.hxx>
22 #include <Message.hxx>
23 #include <Message_Messenger.hxx>
24
25 #include <algorithm>
26
27 IMPLEMENT_STANDARD_RTTIEXT(OpenGl_PBREnvironment, OpenGl_NamedResource)
28
29 //! Constructor of this class saves necessary OpenGL states components which can be changed by OpenGl_PBREnvironment.
30 //! Destructor restores state back.
31 class OpenGl_PBREnvironmentSentry
32 {
33 public:
34
35   OpenGl_PBREnvironmentSentry (const Handle(OpenGl_Context)& theCtx)
36   : myContext (theCtx)
37   {
38     backup();
39     prepare();
40   }
41
42   ~OpenGl_PBREnvironmentSentry()
43   {
44     restore();
45   }
46
47 private:
48
49   void backup()
50   {
51     myContext->core11fwd->glGetIntegerv (GL_FRAMEBUFFER_BINDING, &myFBO);
52     myShaderProgram = myContext->ActiveProgram();
53     for (unsigned int i = 0; i < 4; ++i)
54     {
55       myViewport[i] = myContext->Viewport()[i];
56     }
57     myContext->core11fwd->glGetFloatv (GL_COLOR_CLEAR_VALUE, myClearColor);
58
59     GLboolean aStatus = GL_TRUE;
60     myContext->core11fwd->glGetBooleanv (GL_DEPTH_TEST, &aStatus);
61     myDepthTestWasEnabled = aStatus ? Standard_True : Standard_False;
62     myContext->core11fwd->glGetBooleanv (GL_DEPTH_WRITEMASK, &aStatus);
63     myDepthWrirtingWasEnablig = aStatus ? Standard_True : Standard_False;
64     myContext->core11fwd->glGetBooleanv (GL_SCISSOR_TEST, &aStatus);
65     myScissorTestWasEnabled = aStatus ? Standard_True : Standard_False;
66     myContext->core11fwd->glGetIntegerv (GL_SCISSOR_BOX, myScissorBox);
67   }
68
69   void prepare()
70   {
71     myContext->BindDefaultVao();
72     myContext->core11fwd->glDisable (GL_DEPTH_TEST);
73     myContext->core11fwd->glDepthMask (GL_FALSE);
74     myContext->core11fwd->glDisable (GL_BLEND);
75     myContext->core11fwd->glDisable (GL_SCISSOR_TEST);
76   }
77
78   void restore()
79   {
80     myContext->arbFBO->glBindFramebuffer (GL_DRAW_FRAMEBUFFER, myFBO);
81     myContext->BindProgram (myShaderProgram);
82     myContext->ResizeViewport (myViewport);
83     myContext->core11fwd->glClearColor (myClearColor.r(), myClearColor.g(), myClearColor.b(), myClearColor.a());
84     if (myDepthTestWasEnabled)
85     {
86       myContext->core11fwd->glEnable (GL_DEPTH_TEST);
87     }
88     else
89     {
90       myContext->core11fwd->glDisable (GL_DEPTH_TEST);
91     }
92     myContext->core11fwd->glDepthMask (myDepthWrirtingWasEnablig ? GL_TRUE : GL_FALSE);
93     if (myScissorTestWasEnabled)
94     {
95       myContext->core11fwd->glEnable (GL_SCISSOR_TEST);
96     }
97     else
98     {
99       myContext->core11fwd->glDisable (GL_SCISSOR_TEST);
100     }
101     myContext->core11fwd->glScissor (myScissorBox[0], myScissorBox[1], myScissorBox[2], myScissorBox[3]);
102   }
103
104 private:
105
106   OpenGl_PBREnvironmentSentry            (const OpenGl_PBREnvironmentSentry& );
107   OpenGl_PBREnvironmentSentry& operator= (const OpenGl_PBREnvironmentSentry& );
108
109 private:
110
111   const Handle(OpenGl_Context) myContext;
112   GLint                        myFBO;
113   Handle(OpenGl_ShaderProgram) myShaderProgram;
114   Standard_Boolean             myDepthTestWasEnabled;
115   Standard_Boolean             myDepthWrirtingWasEnablig;
116   Standard_Boolean             myScissorTestWasEnabled;
117   Standard_Integer             myScissorBox[4];
118   Standard_Integer             myViewport[4];
119   Graphic3d_Vec4               myClearColor;
120
121 };
122
123 // =======================================================================
124 // function : Create
125 // purpose  :
126 // =======================================================================
127 Handle(OpenGl_PBREnvironment) OpenGl_PBREnvironment::Create (const Handle(OpenGl_Context)&  theCtx,
128                                                              unsigned int                   thePow2Size,
129                                                              unsigned int                   theLevelsNumber,
130                                                              const TCollection_AsciiString& theId)
131 {
132   if (theCtx->arbFBO == NULL)
133   {
134     return Handle(OpenGl_PBREnvironment)();
135   }
136
137   Handle(OpenGl_PBREnvironment) anEnvironment = new OpenGl_PBREnvironment (theCtx, thePow2Size, theLevelsNumber, theId);
138   if (!anEnvironment->IsComplete())
139   {
140     theCtx->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PORTABILITY, 0, GL_DEBUG_SEVERITY_MEDIUM,
141                          "Warning: PBR environment is not created. PBR material system will be ignored.");
142     anEnvironment->Release (theCtx.get());
143     anEnvironment.Nullify();
144   }
145
146   return anEnvironment;
147 }
148
149 // =======================================================================
150 // function : OpenGl_PBREnvironment
151 // purpose  :
152 // =======================================================================
153 OpenGl_PBREnvironment::OpenGl_PBREnvironment (const Handle(OpenGl_Context)&  theCtx,
154                                               unsigned int                   thePowOf2Size,
155                                               unsigned int                   theSpecMapLevelsNumber,
156                                               const TCollection_AsciiString& theId)
157 : OpenGl_NamedResource (theId),
158   myPow2Size (std::max (1u, thePowOf2Size)),
159   mySpecMapLevelsNumber (std::max (2u, std::min (theSpecMapLevelsNumber, std::max (1u, thePowOf2Size) + 1))),
160   myFBO (OpenGl_FrameBuffer::NO_FRAMEBUFFER),
161   myIsComplete (Standard_False),
162   myIsNeededToBeBound (Standard_True)
163 {
164   OpenGl_PBREnvironmentSentry aSentry (theCtx);
165
166   myIsComplete = initVAO (theCtx)
167               && initTextures (theCtx)
168               && initFBO (theCtx);
169
170   if (myIsComplete)
171   {
172     clear (theCtx);
173   }
174 }
175
176 // =======================================================================
177 // function : Bind
178 // purpose  :
179 // =======================================================================
180 void OpenGl_PBREnvironment::Bind (const Handle(OpenGl_Context)& theCtx)
181 {
182   myIBLMaps[OpenGl_TypeOfIBLMap_DiffuseSH].Bind (theCtx);
183   myIBLMaps[OpenGl_TypeOfIBLMap_Specular] .Bind (theCtx);
184   myIsNeededToBeBound = Standard_False;
185 }
186
187 // =======================================================================
188 // function : Unbind
189 // purpose  :
190 // =======================================================================
191 void OpenGl_PBREnvironment::Unbind (const Handle(OpenGl_Context)& theCtx)
192 {
193   myIBLMaps[OpenGl_TypeOfIBLMap_DiffuseSH].Unbind (theCtx);
194   myIBLMaps[OpenGl_TypeOfIBLMap_Specular] .Unbind (theCtx);
195   myIsNeededToBeBound = Standard_True;
196 }
197
198 // =======================================================================
199 // function : Clear
200 // purpose  :
201 // =======================================================================
202 void OpenGl_PBREnvironment::Clear (const Handle(OpenGl_Context)& theCtx,
203                                    const Graphic3d_Vec3&         theColor)
204 {
205   OpenGl_PBREnvironmentSentry aSentry (theCtx);
206   clear (theCtx, theColor);
207 }
208
209 // =======================================================================
210 // function : Bake
211 // purpose  :
212 // =======================================================================
213 void OpenGl_PBREnvironment::Bake (const Handle(OpenGl_Context)& theCtx,
214                                   const Handle(OpenGl_Texture)& theEnvMap,
215                                   Standard_Boolean              theZIsInverted,
216                                   Standard_Boolean              theIsTopDown,
217                                   Standard_Size                 theDiffMapNbSamples,
218                                   Standard_Size                 theSpecMapNbSamples,
219                                   Standard_ShortReal            theProbability)
220 {
221   Standard_ProgramError_Raise_if (theEnvMap.IsNull(), "'Bake' function of 'OpenGl_PBREnvironment' can't work without source environment map")
222   Standard_RangeError_Raise_if (theProbability > 1.f || theProbability < 0.f, "'probability' parameter in 'Bake' function of 'OpenGl_PBREnvironment' must be in range [0, 1]")
223   Unbind (theCtx);
224   OpenGl_PBREnvironmentSentry aSentry (theCtx);
225   bake (theCtx, theEnvMap, theZIsInverted, theIsTopDown, theDiffMapNbSamples, theSpecMapNbSamples, theProbability);
226 }
227
228 // =======================================================================
229 // function : SizesAreDifferent
230 // purpose  :
231 // =======================================================================
232 bool OpenGl_PBREnvironment::SizesAreDifferent (unsigned int thePow2Size,
233                                                unsigned int theSpecMapLevelsNumber) const
234 {
235   thePow2Size = std::max (1u, thePow2Size);
236   theSpecMapLevelsNumber = std::max (2u, std::min (theSpecMapLevelsNumber, std::max (1u, thePow2Size) + 1));
237   return myPow2Size != thePow2Size
238       || mySpecMapLevelsNumber != theSpecMapLevelsNumber;
239 }
240
241 // =======================================================================
242 // function : Release
243 // purpose  :
244 // =======================================================================
245 void OpenGl_PBREnvironment::Release (OpenGl_Context* theCtx)
246 {
247   if (myFBO != OpenGl_FrameBuffer::NO_FRAMEBUFFER)
248   {
249     if (theCtx != NULL
250      && theCtx->IsValid())
251     {
252       theCtx->arbFBO->glDeleteFramebuffers (1, &myFBO);
253     }
254     myFBO = OpenGl_FrameBuffer::NO_FRAMEBUFFER;
255   }
256   myIBLMaps[OpenGl_TypeOfIBLMap_DiffuseSH].Release(theCtx);
257   myIBLMaps[OpenGl_TypeOfIBLMap_Specular] .Release (theCtx);
258   myVBO.Release (theCtx);
259 }
260
261 // =======================================================================
262 // function : ~OpenGl_PBREnvironment
263 // purpose  :
264 // =======================================================================
265 OpenGl_PBREnvironment::~OpenGl_PBREnvironment()
266 {
267   Release (NULL);
268 }
269
270 // =======================================================================
271 // function : initTextures
272 // purpose  :
273 // =======================================================================
274 bool OpenGl_PBREnvironment::initTextures (const Handle(OpenGl_Context)& theCtx)
275 {
276   myIBLMaps[OpenGl_TypeOfIBLMap_Specular] .Sampler()->Parameters()->SetTextureUnit (theCtx->PBRSpecIBLMapTexUnit());
277   myIBLMaps[OpenGl_TypeOfIBLMap_DiffuseSH].Sampler()->Parameters()->SetTextureUnit (theCtx->PBRDiffIBLMapSHTexUnit());
278   myIBLMaps[OpenGl_TypeOfIBLMap_Specular] .Sampler()->Parameters()->SetFilter (Graphic3d_TOTF_TRILINEAR);
279   myIBLMaps[OpenGl_TypeOfIBLMap_DiffuseSH].Sampler()->Parameters()->SetFilter(Graphic3d_TOTF_NEAREST);
280   myIBLMaps[OpenGl_TypeOfIBLMap_Specular] .Sampler()->Parameters()->SetLevelsRange (mySpecMapLevelsNumber - 1);
281
282   // NVIDIA's driver didn't work properly with 3 channel texture for diffuse SH coefficients so that alpha channel has been added
283   return myIBLMaps[OpenGl_TypeOfIBLMap_DiffuseSH].Init (theCtx,
284                                                         OpenGl_TextureFormat::FindFormat (theCtx, Image_Format_RGBAF, false),
285                                                         Graphic3d_Vec2i (9, 1),
286                                                         Graphic3d_TOT_2D)
287       && myIBLMaps[OpenGl_TypeOfIBLMap_Specular].InitCubeMap (theCtx, Handle(Graphic3d_CubeMap)(),
288                                                               Standard_Size(1) << myPow2Size, Image_Format_RGB, true, false);
289 }
290
291 // =======================================================================
292 // function : initVAO
293 // purpose  :
294 // =======================================================================
295 bool OpenGl_PBREnvironment::initVAO (const Handle(OpenGl_Context)& theCtx)
296 {
297   const float aVertexPos[] =
298   {
299     -1.f, -1.f, 0.f, 0.f,
300      1.f, -1.f, 0.f, 0.f,
301     -1.f,  1.f, 0.f, 0.f,
302      1.f,  1.f, 0.f, 0.f
303   };
304   return myVBO.Init (theCtx, 4, 4, aVertexPos);
305 }
306
307 // =======================================================================
308 // function : initFBO
309 // purpose  :
310 // =======================================================================
311 bool OpenGl_PBREnvironment::initFBO (const Handle(OpenGl_Context)& theCtx)
312 {
313   theCtx->arbFBO->glGenFramebuffers (1, &myFBO);
314   return checkFBOComplentess (theCtx);
315 }
316
317 // =======================================================================
318 // function : processDiffIBLMap
319 // purpose  :
320 // =======================================================================
321 bool OpenGl_PBREnvironment::processDiffIBLMap (const Handle(OpenGl_Context)& theCtx,
322                                                Standard_Boolean              theIsDrawAction,
323                                                Standard_Size                 theNbSamples)
324 {
325   theCtx->arbFBO->glFramebufferTexture2D (GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
326                                           myIBLMaps[OpenGl_TypeOfIBLMap_DiffuseSH].TextureId(), 0);
327   const Standard_Integer aViewport[4] = { 0, 0, 9, 1 };
328   theCtx->ResizeViewport(aViewport);
329   if (theIsDrawAction)
330   {
331     theCtx->ActiveProgram()->SetUniform(theCtx, "occNbSpecIBLLevels", 0);
332     theCtx->ActiveProgram()->SetUniform(theCtx, "uSamplesNum", static_cast<Standard_Integer>(theNbSamples));
333
334     theCtx->core11fwd->glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
335   }
336   else
337   {
338     theCtx->core11fwd->glClear (GL_COLOR_BUFFER_BIT);
339
340     theCtx->core11fwd->glEnable (GL_SCISSOR_TEST);
341     theCtx->core11fwd->glClearColor (0.f, 0.f, 0.f, 1.f);
342     theCtx->core11fwd->glScissor (1, 0, 8, 1);
343     theCtx->core11fwd->glClear (GL_COLOR_BUFFER_BIT);
344   }
345
346   return true;
347 }
348
349 // =======================================================================
350 // function : processSpecIBLMap
351 // purpose  :
352 // =======================================================================
353 bool OpenGl_PBREnvironment::processSpecIBLMap (const Handle(OpenGl_Context)& theCtx,
354                                                Standard_Boolean              theIsDrawAction,
355                                                Standard_Integer              theEnvMapSize,
356                                                Standard_Size                 theNbSamples,
357                                                Standard_ShortReal            theProbability)
358 {
359   if (theIsDrawAction)
360   {
361     theCtx->ActiveProgram()->SetUniform (theCtx, "occNbSpecIBLLevels", Standard_Integer(mySpecMapLevelsNumber));
362     theCtx->ActiveProgram()->SetUniform (theCtx, "uEnvMapSize", theEnvMapSize);
363   }
364
365   for (int aLevelIter = mySpecMapLevelsNumber - 1;; --aLevelIter)
366   {
367     const Standard_Integer aSize = 1 << (myPow2Size - aLevelIter);
368     const Standard_Integer aViewport[4] = { 0, 0, aSize, aSize };
369     theCtx->ResizeViewport (aViewport);
370     if (theIsDrawAction)
371     {
372       Standard_Integer aNbSamples = static_cast<Standard_Integer>(Graphic3d_PBRMaterial::SpecIBLMapSamplesFactor (theProbability, aLevelIter / float (mySpecMapLevelsNumber - 1)) * theNbSamples);
373       theCtx->ActiveProgram()->SetUniform (theCtx, "uSamplesNum", static_cast<Standard_Integer>(aNbSamples));
374       theCtx->ActiveProgram()->SetUniform (theCtx, "uCurrentLevel", aLevelIter);
375     }
376
377     for (Standard_Integer aSideIter = 0; aSideIter < 6; ++aSideIter)
378     {
379       theCtx->arbFBO->glFramebufferTexture2D (GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + aSideIter,
380                                               myIBLMaps[OpenGl_TypeOfIBLMap_Specular].TextureId(), aLevelIter);
381       if (theIsDrawAction)
382       {
383         theCtx->ActiveProgram()->SetUniform(theCtx, "uCurrentSide", aSideIter);
384         theCtx->core11fwd->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
385       }
386       else
387       {
388         theCtx->core11fwd->glClear(GL_COLOR_BUFFER_BIT);
389       }
390     }
391
392     if (aLevelIter == 0)
393     {
394       break;
395     }
396   }
397
398   return true;
399 }
400
401 // =======================================================================
402 // function : checkFBOCompletness
403 // purpose  :
404 // =======================================================================
405 bool OpenGl_PBREnvironment::checkFBOComplentess (const Handle(OpenGl_Context)& theCtx) const
406 {
407   theCtx->arbFBO->glBindFramebuffer (GL_DRAW_FRAMEBUFFER, myFBO);
408   theCtx->arbFBO->glFramebufferTexture2D (GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
409                                           myIBLMaps[OpenGl_TypeOfIBLMap_DiffuseSH].TextureId(), 0);
410   if (theCtx->arbFBO->glCheckFramebufferStatus (GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
411   {
412     return false;
413   }
414   for (Standard_Integer aSideIter = 0; aSideIter < 6; ++aSideIter)
415   {
416     for (unsigned int aLevel = 0; aLevel < mySpecMapLevelsNumber; ++aLevel)
417     {
418       theCtx->arbFBO->glFramebufferTexture2D (GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + aSideIter,
419                                               myIBLMaps[OpenGl_TypeOfIBLMap_Specular].TextureId(), aLevel);
420       if (theCtx->arbFBO->glCheckFramebufferStatus (GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
421       {
422         return false;
423       }
424     }
425   }
426   return true;
427 }
428
429 // =======================================================================
430 // function : bake
431 // purpose  :
432 // =======================================================================
433 void OpenGl_PBREnvironment::bake (const Handle(OpenGl_Context)& theCtx,
434                                   const Handle(OpenGl_Texture)& theEnvMap,
435                                   Standard_Boolean              theZIsInverted,
436                                   Standard_Boolean              theIsTopDown,
437                                   Standard_Size                 theDiffNbSamples,
438                                   Standard_Size                 theSpecNbSamples,
439                                   Standard_ShortReal            theProbability)
440 {
441   myIsNeededToBeBound = Standard_True;
442   if (!theCtx->ShaderManager()->BindPBREnvBakingProgram())
443   {
444     return;
445   }
446   theEnvMap->Bind (theCtx, theCtx->PBRSpecIBLMapTexUnit());
447   theCtx->ActiveProgram()->SetSampler (theCtx, "uEnvMap", theCtx->PBRSpecIBLMapTexUnit());
448   theCtx->ActiveProgram()->SetUniform (theCtx, "uZCoeff", theZIsInverted ? -1 : 1);
449   theCtx->ActiveProgram()->SetUniform (theCtx, "uYCoeff", theIsTopDown ? 1 : -1);
450   theCtx->arbFBO->glBindFramebuffer (GL_DRAW_FRAMEBUFFER, myFBO);
451   myVBO.BindAttribute (theCtx, Graphic3d_TOA_POS);
452
453   OSD_Timer aTimer;
454   aTimer.Start();
455   if (processSpecIBLMap (theCtx, true, theEnvMap->SizeX(), theSpecNbSamples, theProbability)
456    && processDiffIBLMap (theCtx, true, theDiffNbSamples))
457   {
458     Message::DefaultMessenger()->Send(TCollection_AsciiString()
459       + "IBL " + myIBLMaps[OpenGl_TypeOfIBLMap_Specular].SizeX() + "x" + myIBLMaps[OpenGl_TypeOfIBLMap_Specular].SizeY()
460       + " is baked in " + aTimer.ElapsedTime() + " s", Message_Trace);
461   }
462   else
463   {
464     theCtx->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PERFORMANCE, 0, GL_DEBUG_SEVERITY_HIGH,
465       TCollection_AsciiString("Error: baking PBR environment ") + myIBLMaps[OpenGl_TypeOfIBLMap_Specular].SizeX()
466       + "x" + myIBLMaps[OpenGl_TypeOfIBLMap_Specular].SizeY() + " takes too much time!.");
467     clear (theCtx, Graphic3d_Vec3(1.0f));
468   }
469
470   myVBO.UnbindAttribute (theCtx, Graphic3d_TOA_POS);
471   theEnvMap->Unbind (theCtx, theCtx->PBREnvLUTTexUnit());
472 }
473
474 // =======================================================================
475 // function : clear
476 // purpose  :
477 // =======================================================================
478 void OpenGl_PBREnvironment::clear (const Handle(OpenGl_Context)& theCtx,
479                                    const Graphic3d_Vec3&         theColor)
480 {
481   myIsNeededToBeBound = Standard_True;
482   theCtx->arbFBO->glBindFramebuffer (GL_DRAW_FRAMEBUFFER, myFBO);
483   theCtx->core11fwd->glClearColor (theColor.r(), theColor.g(), theColor.b(), 1.f);
484
485   processSpecIBLMap (theCtx, false);
486   processDiffIBLMap (theCtx, false);
487 }