1 /* 2 Copyright © 2020, Inochi2D Project 3 Distributed under the 2-Clause BSD License, see LICENSE file. 4 5 Authors: Luna Nielsen 6 */ 7 module creator.atlas.atlas; 8 import inochi2d; 9 import creator.atlas; 10 import creator.atlas.packer; 11 import bindbc.opengl; 12 13 private { 14 GLuint writeFBO; 15 GLuint writeVAO; 16 GLuint writeVBO; 17 18 GLint atlasMVP; 19 Shader atlasShader; 20 Texture currCanvas; 21 22 void setCanvas(ref Texture canvas) { 23 glBindVertexArray(writeVAO); 24 currCanvas = canvas; 25 26 glBindFramebuffer(GL_FRAMEBUFFER, writeFBO); 27 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, canvas.getTextureId(), 0); 28 glBindFramebuffer(GL_FRAMEBUFFER, 0); 29 } 30 31 void renderToTexture(ref Texture toWrite, rect where, rect uvs) { 32 glBindVertexArray(writeVAO); 33 glBindFramebuffer(GL_FRAMEBUFFER, writeFBO); 34 35 glViewport(0, 0, currCanvas.width, currCanvas.height); 36 glDisable(GL_CULL_FACE); 37 glDisable(GL_DEPTH_TEST); 38 glEnable(GL_BLEND); 39 40 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 41 42 vec2[] bufData = [ 43 vec2(where.left, where.top), 44 vec2(uvs.left, uvs.top), 45 vec2(where.left, where.bottom), 46 vec2(uvs.left, uvs.bottom), 47 vec2(where.right, where.top), 48 vec2(uvs.right, uvs.top), 49 50 vec2(where.right, where.top), 51 vec2(uvs.right, uvs.top), 52 vec2(where.left, where.bottom), 53 vec2(uvs.left, uvs.bottom), 54 vec2(where.right, where.bottom), 55 vec2(uvs.right, uvs.bottom), 56 ]; 57 glBindBuffer(GL_ARRAY_BUFFER, writeVBO); 58 glBufferData(GL_ARRAY_BUFFER, bufData.length*vec2.sizeof, bufData.ptr, GL_DYNAMIC_DRAW); 59 60 glEnableVertexAttribArray(0); 61 glEnableVertexAttribArray(1); 62 glVertexAttribPointer(0, 2, GL_FLOAT, false, vec2.sizeof*2, null); 63 glVertexAttribPointer(1, 2, GL_FLOAT, false, vec2.sizeof*2, cast(void*)vec2.sizeof); 64 65 atlasShader.use(); 66 atlasShader.setUniform(atlasMVP, mat4.orthographic( 67 0, currCanvas.width, currCanvas.height, 0, 0, 2 68 ) * mat4.scaling(1, -1, 1) * mat4.translation(0, -currCanvas.height, -1)); 69 toWrite.bind(); 70 71 glDrawArrays(GL_TRIANGLES, 0, 6); 72 73 glDisableVertexAttribArray(0); 74 glDisableVertexAttribArray(1); 75 glEnable(GL_CULL_FACE); 76 glEnable(GL_DEPTH_TEST); 77 glDisable(GL_BLEND); 78 79 glBindFramebuffer(GL_FRAMEBUFFER, 0); 80 } 81 } 82 83 void incInitAtlassing() { 84 glGenFramebuffers(1, &writeFBO); 85 glGenVertexArrays(1, &writeVAO); 86 glGenBuffers(1, &writeVBO); 87 88 atlasShader = new Shader(import("shaders/atlassing.vert"), import("shaders/atlassing.frag")); 89 atlasMVP = atlasShader.getUniformLocation("mvp"); 90 } 91 92 /** 93 A texture atlas 94 */ 95 class Atlas { 96 public: 97 /** 98 The scale of every element 99 */ 100 float scale = 1; 101 102 /** 103 How much padding in pixels to apply 104 */ 105 int padding = 4; 106 107 /** 108 The underlying textures 109 */ 110 Texture[TextureUsage.COUNT] textures; 111 112 /** 113 MaxRects texture packer 114 */ 115 TexturePacker packer; 116 117 /** 118 Mappings from part UUID to an area on the atlas 119 */ 120 rect[uint] mappings; 121 122 /** 123 Constructs a new atlas with the specified size 124 */ 125 this(size_t atlasSize, int padding, float scale) { 126 this.padding = padding; 127 this.scale = scale; 128 this.resize(atlasSize); 129 } 130 131 /** 132 Packs a part in to a atlas, returns whether this was successful 133 atlasArea contains the area in the atlas that the texture was packed in to. 134 135 UVs should be stretched to cover this area. 136 */ 137 bool pack(Part p) { 138 auto mesh = p.getMesh(); 139 vec2 textureStartOffset = vec2(0, 0); 140 vec2 textureEndOffset = vec2(0, 0); 141 142 // Calculate how much of the texture is actually used in UV coordinates 143 vec4 uvArea = vec4(1, 1, 0, 0); 144 foreach(vec2 uv; mesh.uvs) { 145 if (uv.x < uvArea.x) uvArea.x = uv.x; 146 if (uv.y < uvArea.y) uvArea.y = uv.y; 147 if (uv.x > uvArea.z) uvArea.z = uv.x; 148 if (uv.y > uvArea.w) uvArea.w = uv.y; 149 } 150 if (uvArea.x < 0) { 151 textureStartOffset.x = -uvArea.x; 152 uvArea.x = 0; 153 } 154 if (uvArea.y < 0) { 155 textureStartOffset.y = -uvArea.y; 156 uvArea.y = 0; 157 } 158 if (uvArea.z > 1) { 159 textureEndOffset.x = uvArea.z - 1; 160 uvArea.z = 1; 161 } 162 if (uvArea.w > 1) { 163 textureEndOffset.y = uvArea.w - 1; 164 uvArea.w = 1; 165 } 166 167 // uvRect is always between (0..1) 168 rect uvRect = rect(uvArea.x, uvArea.y, uvArea.z-uvArea.x, uvArea.w-uvArea.y); 169 // boundingUVRect may exceed (0..1) if mesh exceeds the texture boundary. 170 rect boundingUVRect = rect(uvArea.x - textureStartOffset.x, uvArea.y - textureStartOffset.y, 171 uvArea.z - uvArea.x + textureStartOffset.x + textureEndOffset.x, uvArea.w - uvArea.y + textureStartOffset.y + textureEndOffset.y); 172 173 vec2i size = vec2i( 174 cast(int)((p.textures[0].width*boundingUVRect.width)*scale)+(padding*2), 175 cast(int)((p.textures[0].height*boundingUVRect.height)*scale)+(padding*2) 176 ); 177 178 // Get a slot for the texture in the atlas 179 vec4 atlasArea = packer.packTexture(size); 180 181 // Could not fit texture, return false 182 if (atlasArea == vec4i(0, 0, 0, 0)) return false; 183 184 textureStartOffset.x *= p.textures[0].width * scale; 185 textureStartOffset.y *= p.textures[0].height * scale; 186 textureEndOffset.x *= p.textures[0].width * scale; 187 textureEndOffset.y *= p.textures[0].height * scale; 188 // Render textures in to our atlas 189 foreach(i, ref Texture texture; p.textures) { 190 if (texture) { 191 setCanvas(textures[i]); 192 // where is the calculated texture boundary #2, specifying the area which texture is copied. (alsway between 0..1 in UV position) 193 rect where = rect(atlasArea.x+textureStartOffset.x+padding, atlasArea.y+textureStartOffset.y+padding, 194 atlasArea.z-(padding*2)-textureStartOffset.x - textureEndOffset.x, atlasArea.w-(padding*2)-textureStartOffset.y - textureEndOffset.y); 195 mappings[p.uuid] = rect(atlasArea.x+padding, atlasArea.y+padding, atlasArea.z-padding*2, atlasArea.w-padding*2); 196 renderToTexture(texture, where, uvRect); 197 } 198 } 199 return true; 200 } 201 202 /** 203 Resizes the atlas 204 */ 205 void resize(size_t atlasSize) { 206 packer = new TexturePacker(vec2i(cast(int)atlasSize, cast(int)atlasSize)); 207 foreach(i; 0..textures.length) { 208 if (textures[i]) textures[i].dispose(); 209 210 int channels = i == TextureUsage.Albedo ? 4 : 3; 211 textures[i] = new Texture(cast(int)atlasSize, cast(int)atlasSize, channels); 212 // Clear the new texture 213 glBindFramebuffer(GL_FRAMEBUFFER, writeFBO); 214 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 215 textures[i].getTextureId(), 0); 216 glClearColor(0, 0, 0, 0); 217 glClear(GL_COLOR_BUFFER_BIT); 218 glBindFramebuffer(GL_FRAMEBUFFER, 0); 219 } 220 } 221 222 /** 223 Finalize the atlas 224 */ 225 void finalize() { 226 foreach(texture; textures) { 227 texture.genMipmap(); 228 } 229 } 230 231 /** 232 Clears the texture packer 233 */ 234 void clear() { 235 mappings.clear(); 236 packer.clear(); 237 } 238 }