Last active
November 1, 2025 07:09
-
-
Save castano/4e58e4212bf337c957ce020287554d9d to your computer and use it in GitHub Desktop.
Generates a DDS cubemap from a latlong HDR image.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define STB_IMAGE_IMPLEMENTATION | |
| #include "stb_image.h" | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <math.h> | |
| #include <stdint.h> | |
| #ifndef M_PI | |
| #define M_PI 3.14159265358979323846 | |
| #endif | |
| typedef struct { float x, y, z; } Vec3; | |
| inline Vec3 normalize(Vec3 v) { | |
| float l = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); | |
| return (Vec3){ v.x/l, v.y/l, v.z/l }; | |
| } | |
| inline void dir_to_uv(Vec3 d, float *u, float *v) { | |
| float phi = atan2f(d.z, d.x); | |
| float theta = acosf(d.y); | |
| *u = (phi + M_PI) / (2.0f * M_PI); | |
| *v = theta / M_PI; | |
| } | |
| inline Vec3 face_dir(int face, float u, float v) { | |
| float x = 2.0f * u - 1.0f; | |
| float y = 2.0f * v - 1.0f; | |
| switch(face) { | |
| case 0: return normalize((Vec3){ 1, -y, -x }); // +X | |
| case 1: return normalize((Vec3){-1, -y, x }); // -X | |
| case 2: return normalize((Vec3){ x, 1, y }); // +Y | |
| case 3: return normalize((Vec3){ x, -1, -y }); // -Y | |
| case 4: return normalize((Vec3){ x, -y, 1 }); // +Z | |
| case 5: return normalize((Vec3){-x, -y, -1 }); // -Z | |
| } | |
| return (Vec3){0,0,0}; | |
| } | |
| inline void sample_latlong(float *img, int w, int h, int n, float u, float v, float *out) | |
| { | |
| // Wrap horizontally and clamp vertically | |
| u = fmodf(u, 1.0f); | |
| if (u < 0.0f) u += 1.0f; | |
| v = fminf(fmaxf(v, 0.0f), 1.0f); | |
| float x = u * (w - 1); | |
| float y = v * (h - 1); | |
| int x0 = (int)floorf(x); | |
| int y0 = (int)floorf(y); | |
| int x1 = (x0 + 1) % w; | |
| int y1 = y0 < h - 1 ? y0 + 1 : h - 1; | |
| float fx = x - x0; | |
| float fy = y - y0; | |
| const float *p00 = img + (y0 * w + x0) * n; | |
| const float *p10 = img + (y0 * w + x1) * n; | |
| const float *p01 = img + (y1 * w + x0) * n; | |
| const float *p11 = img + (y1 * w + x1) * n; | |
| for (int i = 0; i < n; ++i) { | |
| float c0 = p00[i] * (1 - fx) + p10[i] * fx; | |
| float c1 = p01[i] * (1 - fx) + p11[i] * fx; | |
| out[i] = c0 * (1 - fy) + c1 * fy; | |
| } | |
| } | |
| #define DDS_MAGIC 0x20534444 // "DDS " | |
| #define DDS_HEADER_FLAGS_TEXTURE 0x00001007 | |
| #define DDSCAPS_TEXTURE 0x00001000 | |
| #define DDSCAPS2_CUBEMAP 0x00000200 | |
| #define DDSCAPS2_CUBEMAP_ALL_FACES 0x0000FC00 | |
| #define DDPF_FOURCC 0x00000004 | |
| #define FOURCC_DX10 0x30315844 // "DX10" | |
| #define DXGI_FORMAT_R32G32B32_FLOAT 6 | |
| #define D3D10_RESOURCE_DIMENSION_TEXTURE2D 3 | |
| #define D3D10_RESOURCE_MISC_TEXTURECUBE 0x4 | |
| #pragma pack(push,1) | |
| typedef struct { | |
| uint32_t size; | |
| uint32_t flags; | |
| uint32_t height; | |
| uint32_t width; | |
| uint32_t pitchOrLinearSize; | |
| uint32_t depth; | |
| uint32_t mipMapCount; | |
| uint32_t reserved1[11]; | |
| struct { | |
| uint32_t size; | |
| uint32_t flags; | |
| uint32_t fourCC; | |
| uint32_t rgbBitCount; | |
| uint32_t rBitMask; | |
| uint32_t gBitMask; | |
| uint32_t bBitMask; | |
| uint32_t aBitMask; | |
| } ddspf; | |
| uint32_t caps; | |
| uint32_t caps2; | |
| uint32_t caps3; | |
| uint32_t caps4; | |
| uint32_t reserved2; | |
| } DDS_HEADER; | |
| typedef struct { | |
| uint32_t dxgiFormat; | |
| uint32_t resourceDimension; | |
| uint32_t miscFlag; | |
| uint32_t arraySize; | |
| uint32_t miscFlags2; | |
| } DDS_HEADER_DXT10; | |
| #pragma pack(pop) | |
| static void write_dds_cubemap_f32(const char *filename, float *faces[6], int size) | |
| { | |
| FILE *f = fopen(filename, "wb"); | |
| if (!f) { | |
| perror("fopen"); | |
| return; | |
| } | |
| uint32_t magic = DDS_MAGIC; | |
| fwrite(&magic, sizeof(magic), 1, f); | |
| DDS_HEADER header = {0}; | |
| header.size = sizeof(DDS_HEADER); | |
| header.flags = DDS_HEADER_FLAGS_TEXTURE; | |
| header.height = size; | |
| header.width = size; | |
| header.pitchOrLinearSize = size * size * 3 * sizeof(float); | |
| header.ddspf.size = 32; | |
| header.ddspf.flags = DDPF_FOURCC; | |
| header.ddspf.fourCC = FOURCC_DX10; | |
| header.caps = DDSCAPS_TEXTURE; | |
| header.caps2 = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_ALL_FACES; | |
| fwrite(&header, sizeof(header), 1, f); | |
| DDS_HEADER_DXT10 dx10 = {0}; | |
| dx10.dxgiFormat = DXGI_FORMAT_R32G32B32_FLOAT; | |
| dx10.resourceDimension = D3D10_RESOURCE_DIMENSION_TEXTURE2D; | |
| dx10.miscFlag = D3D10_RESOURCE_MISC_TEXTURECUBE; | |
| dx10.arraySize = 6; | |
| dx10.miscFlags2 = 0; | |
| fwrite(&dx10, sizeof(dx10), 1, f); | |
| for (int i = 0; i < 6; ++i) | |
| fwrite(faces[i], sizeof(float), size * size * 3, f); | |
| fclose(f); | |
| } | |
| int main(int argc, char **argv) { | |
| if (argc < 3) { | |
| printf("Usage: %s <input.hdr> <size>\n", argv[0]); | |
| return 1; | |
| } | |
| int w, h, n; | |
| float *img = stbi_loadf(argv[1], &w, &h, &n, 3); | |
| if (!img) { | |
| fprintf(stderr, "Error loading image %s\n", argv[1]); | |
| return 1; | |
| } | |
| int size = atoi(argv[2]); | |
| float *faces[6]; | |
| for (int f=0; f<6; ++f) faces[f] = (float*)malloc(size*size*3*sizeof(float)); | |
| for (int f=0; f<6; ++f) | |
| for (int y=0; y<size; ++y) | |
| for (int x=0; x<size; ++x) { | |
| float u = (x + 0.5f)/size; | |
| float v = (y + 0.5f)/size; | |
| Vec3 dir = face_dir(f, u, v); | |
| float su, sv; | |
| dir_to_uv(dir, &su, &sv); | |
| sample_latlong(img, w, h, 3, su, sv, &faces[f][(y*size+x)*3]); | |
| } | |
| write_dds_cubemap_f32("output.dds", faces, size); | |
| for (int f=0; f<6; ++f) free(faces[f]); | |
| stbi_image_free(img); | |
| printf("Cubemap written to output.dds\n"); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment