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 }