1 /*
2 Copyright (c) 2019-2021 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 
28 module dagon.graphics.cubemap;
29 
30 import std.stdio;
31 import std.math;
32 import std.traits;
33 
34 import dlib.core.ownership;
35 import dlib.image.color;
36 import dlib.image.image;
37 import dlib.math.vector;
38 import dlib.math.matrix;
39 import dlib.math.transformation;
40 import dlib.math.utils;
41 
42 import dagon.core.bindings;
43 import dagon.graphics.texture;
44 import dagon.graphics.containerimage;
45 
46 enum CubeFace
47 {
48     PositiveX = GL_TEXTURE_CUBE_MAP_POSITIVE_X,
49     NegativeX = GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
50     PositiveY = GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
51     NegativeY = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
52     PositiveZ = GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
53     NegativeZ = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
54 }
55 
56 class Cubemap: Texture
57 {
58     this(Owner o)
59     {
60         super(o);
61     }
62 
63     this(uint resolution, Owner o)
64     {
65         super(o);
66         initialize(resolution);
67     }
68 
69     ~this()
70     {
71         release();
72     }
73 
74     void initialize()
75     {
76         releaseGLTexture();
77 
78         glActiveTexture(GL_TEXTURE0);
79 
80         glGenTextures(1, &tex);
81         glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
82 
83         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
84         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
85         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
86         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
87         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
88 
89         glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
90     }
91 
92     void initialize(uint resolution)
93     {
94         initialize();
95 
96         width = resolution;
97         height = resolution;
98         
99         glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
100 
101         glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
102         glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
103         glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
104         glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
105         glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
106         glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
107 
108         glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
109     }
110 
111     void setFaceImage(CubeFace face, SuperImage img)
112     {
113         if (img.width != img.height)
114         {
115             writefln("Cubemap face image must be square");
116             return;
117         }
118 
119         width = img.width;
120         height = img.height;
121         
122         TextureFormat tf;
123         if (detectTextureFormat(img, tf))
124         {
125             format = tf.format;
126             intFormat = tf.internalFormat;
127             type = tf.pixelType;
128             
129             glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
130             if (tf.compressed)
131             {
132                 uint size = ((width + 3) / 4) * ((height + 3) / 4) * tf.blockSize;
133                 glCompressedTexImage2D(face, 0, intFormat, width, height, 0, size, cast(void*)img.data.ptr);
134             }
135             else
136             {
137                 glTexImage2D(face, 0, intFormat, width, height, 0, format, type, cast(void*)img.data.ptr);
138             }
139             glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
140         }
141         else
142         {
143             writefln("Unsupported pixel format %s", img.pixelFormat);
144         }
145     }
146     
147     void fromEquirectangularMap(Texture tex)
148     {
149         fromEquirectangularMap(tex.image);
150     }
151 
152     void fromEquirectangularMap(SuperImage envmap)
153     {
154         SuperImage faceImage = envmap.createSameFormat(width, width);
155 
156         foreach(i, face; EnumMembers!CubeFace)
157         {
158             Matrix4x4f dirTransform = cubeFaceMatrix(face);
159 
160             foreach(x; 0..width)
161             foreach(y; 0..width)
162             {
163                 float cubex = (cast(float)x / cast(float)width) * 2.0f - 1.0f;
164                 float cubey = (1.0f - cast(float)y / cast(float)width) * 2.0f - 1.0f;
165                 Vector3f dir = Vector3f(cubex, cubey, 1.0f).normalized * dirTransform;
166                 Vector2f uv = equirectProj(dir);
167                 Color4f c = bilinearPixel(envmap, uv.x * envmap.width, uv.y * envmap.height);
168                 faceImage[x, y] = c;
169             }
170 
171             setFaceImage(face, faceImage);
172         }
173 
174         faceImage.free();
175     }
176     
177     void fromContainerImage(ContainerImage img)
178     {
179         initialize();
180         
181         if (!img.isCubemap)
182         {
183             writefln("Image is not a cubemap");
184             return;
185         }
186         
187         TextureFormat tf;
188         if (!detectTextureFormat(img, tf))
189         {
190             writefln("Unsupported pixel format %s for a cubemap", img.pixelFormat);
191             return;
192         }
193         else if (tf.compressed)
194         {
195             writefln("Unsupported pixel format %s for a cubemap", img.pixelFormat);
196             return;
197         }
198         
199         width = img.width;
200         height = img.height;
201         numMipmapLevels = img.mipLevels;
202         
203         format = tf.format;
204         intFormat = tf.internalFormat;
205         type = tf.pixelType;
206         
207         uint pSize = pixelSize(img.pixelFormat);
208         
209         glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
210         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0);
211         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, numMipmapLevels - 1);
212         
213         ubyte* data = img.data.ptr;
214         uint offset = 0;
215         
216         foreach(face; EnumMembers!CubeFace)
217         {
218             uint w = width;
219             uint h = height;
220             for (uint i = 0; i < numMipmapLevels; i++)
221             {
222                 uint size = w * h * pSize;
223                 glTexImage2D(face, i, intFormat, w, h, 0, format, type, cast(void*)(data + offset));
224                 offset += size;
225                 w /= 2;
226                 h /= 2;
227                 if (offset >= img.data.length)
228                 {
229                     writeln("Incomplete data");
230                     break;
231                 }
232             }
233         }
234         
235         glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
236     }
237     
238     void fromImage(SuperImage img, uint resolution = 512)
239     {
240         ContainerImage cImage = cast(ContainerImage)img;
241         if (cImage)
242         {
243             fromContainerImage(cImage);
244         }
245         else
246         {
247             initialize(resolution);
248             fromEquirectangularMap(img);
249         }
250     }
251 
252     override void bind()
253     {
254         if (glIsTexture(tex))
255         {
256             glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
257 
258             if (!mipmapGenerated && useMipmapFiltering)
259             {
260                 glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
261                 mipmapGenerated = true;
262             }
263         }
264     }
265 
266     override void unbind()
267     {
268         glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
269     }
270 
271     void invalidateMipmap()
272     {
273         mipmapGenerated = false;
274     }
275 }
276 
277 uint pixelSize(uint pixelFormat)
278 {
279     uint s = 0;
280     switch(pixelFormat)
281     {
282         case ContainerImageFormat.R8:      s = 1; break;
283         case ContainerImageFormat.RG8:     s = 2; break;
284         case ContainerImageFormat.RGB8:    s = 3; break;
285         case ContainerImageFormat.RGBA8:   s = 4; break;
286         case ContainerImageFormat.RGBAF32: s = 16; break;
287         case ContainerImageFormat.RGBAF16: s = 8; break;
288         default: break;
289     }
290     return s;
291 }
292 
293 Matrix4x4f cubeFaceMatrix(CubeFace cf)
294 {
295     switch(cf)
296     {
297         case CubeFace.PositiveX:
298             return rotationMatrix(1, degtorad(-90.0f));
299         case CubeFace.NegativeX:
300             return rotationMatrix(1, degtorad(90.0f));
301         case CubeFace.PositiveY:
302             return rotationMatrix(0, degtorad(90.0f));
303         case CubeFace.NegativeY:
304             return rotationMatrix(0, degtorad(-90.0f));
305         case CubeFace.PositiveZ:
306             return rotationMatrix(1, degtorad(0.0f));
307         case CubeFace.NegativeZ:
308             return rotationMatrix(1, degtorad(180.0f));
309         default:
310             return Matrix4x4f.identity;
311     }
312 }
313 
314 Matrix4x4f cubeFaceCameraMatrix(CubeFace cf, Vector3f pos)
315 {
316     Matrix4x4f m;
317     switch(cf)
318     {
319         case CubeFace.PositiveX:
320             m = rotationMatrix(1, degtorad(90.0f)) * translationMatrix(pos) * rotationMatrix(1, degtorad(90.0f)) * rotationMatrix(2, degtorad(180.0f));
321             break;
322         case CubeFace.NegativeX:
323             m = rotationMatrix(1, degtorad(90.0f)) * translationMatrix(pos) * rotationMatrix(1, degtorad(-90.0f)) * rotationMatrix(2, degtorad(180.0f));
324             break;
325         case CubeFace.PositiveY:
326             m = rotationMatrix(1, degtorad(90.0f)) * translationMatrix(pos) * rotationMatrix(1, degtorad(0.0f)) * rotationMatrix(0, degtorad(-90.0f));
327             break;
328         case CubeFace.NegativeY:
329             m = rotationMatrix(1, degtorad(90.0f)) * translationMatrix(pos) * rotationMatrix(1, degtorad(0.0f)) * rotationMatrix(0, degtorad(90.0f));
330             break;
331         case CubeFace.PositiveZ:
332             m = rotationMatrix(1, degtorad(90.0f)) * translationMatrix(pos) * rotationMatrix(1, degtorad(180.0f)) * rotationMatrix(2, degtorad(180.0f));
333             break;
334         case CubeFace.NegativeZ:
335             m = rotationMatrix(1, degtorad(90.0f)) * translationMatrix(pos) * rotationMatrix(1, degtorad(0.0f)) * rotationMatrix(2, degtorad(180.0f));
336             break;
337         default:
338             m = Matrix4x4f.identity; break;
339     }
340     return m;
341 }
342 
343 Vector2f equirectProj(Vector3f dir)
344 {
345     float phi = acos(dir.y);
346     float theta = atan2(dir.x, dir.z) + PI;
347     return Vector2f(theta / (PI * 2.0f), phi / PI);
348 }