{"id":19378646,"url":"https://github.com/aniketrajnish/unity-webgpu-pbr-maps-generator","last_synced_at":"2026-05-18T00:32:40.677Z","repository":{"id":230280601,"uuid":"778979176","full_name":"aniketrajnish/Unity-WebGPU-PBR-Maps-Generator","owner":"aniketrajnish","description":"Helps generate PBR maps from a base/diffuse map depending on the workflow chosen using WebGPU.","archived":false,"fork":false,"pushed_at":"2024-08-06T18:50:24.000Z","size":209479,"stargazers_count":13,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-07-11T23:46:13.948Z","etag":null,"topics":["pbr","pbr-materials","pbr-shading","unity","unity-webgpu","unity3d","webgpu"],"latest_commit_sha":null,"homepage":"http://makra.wtf/Unity-WebGPU-PBR-Maps-Generator/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aniketrajnish.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-28T19:43:25.000Z","updated_at":"2025-05-10T04:16:35.000Z","dependencies_parsed_at":"2024-08-05T07:33:29.705Z","dependency_job_id":null,"html_url":"https://github.com/aniketrajnish/Unity-WebGPU-PBR-Maps-Generator","commit_stats":null,"previous_names":["aniketrajnish/pbr-maps-generator","aniketrajnish/unity-webgpu-pbr-maps-generator"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/aniketrajnish/Unity-WebGPU-PBR-Maps-Generator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aniketrajnish%2FUnity-WebGPU-PBR-Maps-Generator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aniketrajnish%2FUnity-WebGPU-PBR-Maps-Generator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aniketrajnish%2FUnity-WebGPU-PBR-Maps-Generator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aniketrajnish%2FUnity-WebGPU-PBR-Maps-Generator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aniketrajnish","download_url":"https://codeload.github.com/aniketrajnish/Unity-WebGPU-PBR-Maps-Generator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aniketrajnish%2FUnity-WebGPU-PBR-Maps-Generator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33160476,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T22:39:12.733Z","status":"ssl_error","status_checked_at":"2026-05-17T22:39:10.741Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["pbr","pbr-materials","pbr-shading","unity","unity-webgpu","unity3d","webgpu"],"created_at":"2024-11-10T09:06:33.569Z","updated_at":"2026-05-18T00:32:35.668Z","avatar_url":"https://github.com/aniketrajnish.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Unity-WebGPU-PBR-Maps-Generator\nHelps generate PBR maps from a base/diffuse map depending on the workflow chosen. This project demonstrates the power of `WebGPU` by implementing a Physically Based Rendering (PBR) map generator that runs efficiently in web browsers. It showcases the use of compute shaders for GPU-accelerated texture processing, providing a significant performance boost over traditional CPU-based methods. The output of course is not very fancy, as we don't have access to the real height information of the texture, and we're just making a guess based on the color information, which could be a hit or miss. But it's a good starting point to prototype some PBR textures quickly!\n\nYou can try out the project here- [https://makra.wtf/Unity-WebGPU-PBR-Maps-Generator/](https://makra.wtf/Unity-WebGPU-PBR-Maps-Generator/). \u003cbr\u003e The page might take a while to load for the first time but once it's cached, it should load quickly on subsequent visits.\n\nhttps://github.com/user-attachments/assets/ead85097-f6dc-42b5-89a2-8e2ab5d76f4b\n\n## Features\n- Generate various PBR maps from a single base texture:\n  - Height Map\n  - Normal Map\n  - Ambient Occlusion (AO) Map\n  - Roughness Map\n  - Metallic Map\n  - Specular Map\n  - Glossiness Map\n- Choose between Metallic-Roughness and Specular-Glossiness workflows.\n- GPU acceleration using compute shaders using `Unity`'s new `WebGPU` backend.\n- CPU fallback for devices without GPU that supports 64 threads per block. \n- Real-time preview of generated maps along with the option to download them.\n\n\u003cdiv align =center\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003cimg src=\"https://github.com/user-attachments/assets/047ddb0b-71a4-40d1-93ff-5210c8fe4690\" width=\"75\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src=\"https://github.com/user-attachments/assets/070bda0c-1716-4925-9bad-e8c8748e5bb4\" width=\"75\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src=\"https://github.com/user-attachments/assets/5eaa7e0a-652e-49f0-9435-1829bf3415b5\" width=\"75\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src=\"https://github.com/user-attachments/assets/8f51a7fd-3863-4125-b90d-4b0aeff0b94c\" width=\"75\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src=\"https://github.com/user-attachments/assets/9fbdd95c-32b6-481b-9670-71a8d0398cc8\" width=\"75\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src=\"https://github.com/user-attachments/assets/3ce171b2-579f-4c6e-8dfa-fbad3e6da583\" width=\"75\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src=\"https://github.com/user-attachments/assets/5d214885-05fe-4a81-ab73-839502ab57a2\" width=\"75\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src=\"https://github.com/user-attachments/assets/101e1d5e-b990-4892-ae25-53c024d9e2b7\" width=\"75\"\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003eBase\u003c/td\u003e\n    \u003ctd align=\"center\"\u003eHeight\u003c/td\u003e\n    \u003ctd align=\"center\"\u003eNormal\u003c/td\u003e\n    \u003ctd align=\"center\"\u003eAO\u003c/td\u003e\n    \u003ctd align=\"center\"\u003eMetallic\u003c/td\u003e\n    \u003ctd align=\"center\"\u003eRoughness\u003c/td\u003e\n    \u003ctd align=\"center\"\u003eSpecular\u003c/td\u003e\n    \u003ctd align=\"center\"\u003eGlossiness\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nPBR maps generated from a single base texture using the tool\n\u003c/div\u003e\n\n## WebGPU advantage\n\nHere's a comparison of CPU vs GPU methods for generating a normal map:\n\n### CPU Method\n\n```csharp\nprivate static Texture2D CPUConvertToNormalMap(Texture2D heightMap)\n{\n    Texture2D normalMap = new Texture2D(heightMap.width, heightMap.height, TextureFormat.RGBA32, false);\n    for (int y = 0; y \u003c heightMap.height; y++)\n    {\n        for (int x = 0; x \u003c heightMap.width; x++)\n        {\n            float left = GetPixelHeight(heightMap, x - 1, y);\n            float right = GetPixelHeight(heightMap, x + 1, y);\n            float top = GetPixelHeight(heightMap, x, y - 1);\n            float bottom = GetPixelHeight(heightMap, x, y + 1);\n            \n            Vector3 normal = new Vector3(left - right, bottom - top, 1).normalized;\n            normal = normal * 0.5f + Vector3.one * 0.5f;\n            \n            normalMap.SetPixel(x, y, new Color(normal.x, normal.y, normal.z, 1));\n        }\n    }\n    normalMap.Apply();\n    return normalMap;\n}\n```\n\n### GPU Method\n\n```glsl\n#pragma kernel CSMain\n\nTexture2D\u003cfloat\u003e HeightMap;\nRWTexture2D\u003cfloat4\u003e NormalMap;\nuint2 TextureSize;\n\n[numthreads(8,8,1)]\nvoid CSMain (uint3 id : SV_DispatchThreadID)\n{\n    if (id.x \u003e= TextureSize.x || id.y \u003e= TextureSize.y)\n        return;\n\n    float left = HeightMap[uint2(max(id.x - 1, 0), id.y)];\n    float right = HeightMap[uint2(min(id.x + 1, TextureSize.x - 1), id.y)];\n    float top = HeightMap[uint2(id.x, max(id.y - 1, 0))];\n    float bottom = HeightMap[uint2(id.x, min(id.y + 1, TextureSize.y - 1))];\n    \n    float3 normal = normalize(float3(left - right, bottom - top, 1));\n    normal = normal * 0.5 + 0.5;\n    \n    NormalMap[id.xy] = float4(normal, 1);\n}\n```\n\nUsing compute shaders we create 8x8 threads per block, which is the maximum supported by the `WebGPU` backend. This allows us to process 64 pixels in parallel, providing a significant performance boost over the CPU method.\n\nThese are the results of the performance comparison using a system with an `Intel Core i9-13900HX` CPU and an NVIDIA GeForce `RTX 4070` GPU and  `32 GB` of RAM:\n\nhttps://github.com/user-attachments/assets/149acec5-1174-45ce-abce-d7a763bcffad\n\n\u003cdiv align=center\u003e\n  \u003ctable style=\"margin: auto;\"\u003e\n    \u003cthead\u003e\n      \u003ctr\u003e\n        \u003cth\u003eMap Type\u003c/th\u003e\n        \u003cth\u003eResolution\u003c/th\u003e\n        \u003cth\u003eCPU Time (ms)\u003c/th\u003e\n        \u003cth\u003eGPU Time (ms)\u003c/th\u003e\n        \u003cth\u003eSpeedup Factor\u003c/th\u003e\n      \u003c/tr\u003e\n    \u003c/thead\u003e\n    \u003ctbody\u003e\n      \u003ctr\u003e\n        \u003ctd\u003eHeight\u003c/td\u003e\n        \u003ctd\u003e512x512\u003c/td\u003e\n        \u003ctd\u003e350\u003c/td\u003e\n        \u003ctd\u003e20\u003c/td\u003e\n        \u003ctd\u003e17.5x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e1024x1024\u003c/td\u003e\n        \u003ctd\u003e1500\u003c/td\u003e\n        \u003ctd\u003e50\u003c/td\u003e\n        \u003ctd\u003e30x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e2048x2048\u003c/td\u003e\n        \u003ctd\u003e6200\u003c/td\u003e\n        \u003ctd\u003e150\u003c/td\u003e\n        \u003ctd\u003e41.3x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003eNormal\u003c/td\u003e\n        \u003ctd\u003e512x512\u003c/td\u003e\n        \u003ctd\u003e500\u003c/td\u003e\n        \u003ctd\u003e30\u003c/td\u003e\n        \u003ctd\u003e16.7x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e1024x1024\u003c/td\u003e\n        \u003ctd\u003e2000\u003c/td\u003e\n        \u003ctd\u003e80\u003c/td\u003e\n        \u003ctd\u003e25x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e2048x2048\u003c/td\u003e\n        \u003ctd\u003e8000\u003c/td\u003e\n        \u003ctd\u003e250\u003c/td\u003e\n        \u003ctd\u003e32x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003eAO\u003c/td\u003e\n        \u003ctd\u003e512x512\u003c/td\u003e\n        \u003ctd\u003e450\u003c/td\u003e\n        \u003ctd\u003e20\u003c/td\u003e\n        \u003ctd\u003e22.5x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e1024x1024\u003c/td\u003e\n        \u003ctd\u003e1800\u003c/td\u003e\n        \u003ctd\u003e60\u003c/td\u003e\n        \u003ctd\u003e30x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e2048x2048\u003c/td\u003e\n        \u003ctd\u003e7200\u003c/td\u003e\n        \u003ctd\u003e180\u003c/td\u003e\n        \u003ctd\u003e40x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003eRoughness\u003c/td\u003e\n        \u003ctd\u003e512x512\u003c/td\u003e\n        \u003ctd\u003e550\u003c/td\u003e\n        \u003ctd\u003e30\u003c/td\u003e\n        \u003ctd\u003e18.3x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e1024x1024\u003c/td\u003e\n        \u003ctd\u003e2200\u003c/td\u003e\n        \u003ctd\u003e70\u003c/td\u003e\n        \u003ctd\u003e31.4x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e2048x2048\u003c/td\u003e\n        \u003ctd\u003e8800\u003c/td\u003e\n        \u003ctd\u003e220\u003c/td\u003e\n        \u003ctd\u003e40x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003eMetallic\u003c/td\u003e\n        \u003ctd\u003e512x512\u003c/td\u003e\n        \u003ctd\u003e400\u003c/td\u003e\n        \u003ctd\u003e20\u003c/td\u003e\n        \u003ctd\u003e20x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e1024x1024\u003c/td\u003e\n        \u003ctd\u003e1600\u003c/td\u003e\n        \u003ctd\u003e50\u003c/td\u003e\n        \u003ctd\u003e32x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e2048x2048\u003c/td\u003e\n        \u003ctd\u003e6400\u003c/td\u003e\n        \u003ctd\u003e160\u003c/td\u003e\n        \u003ctd\u003e40x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003eSpecular\u003c/td\u003e\n        \u003ctd\u003e512x512\u003c/td\u003e\n        \u003ctd\u003e420\u003c/td\u003e\n        \u003ctd\u003e20\u003c/td\u003e\n        \u003ctd\u003e21x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e1024x1024\u003c/td\u003e\n        \u003ctd\u003e1700\u003c/td\u003e\n        \u003ctd\u003e60\u003c/td\u003e\n        \u003ctd\u003e28.3x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e2048x2048\u003c/td\u003e\n        \u003ctd\u003e6800\u003c/td\u003e\n        \u003ctd\u003e180\u003c/td\u003e\n        \u003ctd\u003e37.8x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003eGlossiness\u003c/td\u003e\n        \u003ctd\u003e512x512\u003c/td\u003e\n        \u003ctd\u003e600\u003c/td\u003e\n        \u003ctd\u003e40\u003c/td\u003e\n        \u003ctd\u003e15x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e1024x1024\u003c/td\u003e\n        \u003ctd\u003e2400\u003c/td\u003e\n        \u003ctd\u003e90\u003c/td\u003e\n        \u003ctd\u003e26.7x\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e2048x2048\u003c/td\u003e\n        \u003ctd\u003e9600\u003c/td\u003e\n        \u003ctd\u003e280\u003c/td\u003e\n        \u003ctd\u003e34.3x\u003c/td\u003e\n      \u003c/tr\u003e\n    \u003c/tbody\u003e\n  \u003c/table\u003e\n\u003c/div\u003e\n\n\u003cdiv align =center\u003e\n  \u003cimg src=\"https://github.com/user-attachments/assets/b4a7f72f-f147-426e-813a-37ed016df6c1\" alt=\"plt\" style=\"width: 80%;\"/\u003e\n\u003c/div\u003e\n\nAs we can see from the table and graph, the GPU method consistently outperforms the CPU method, with the performance gap widening as the texture resolution increases. This demonstrates the scalability and efficiency of compute shaders for texture processing tasks.\n\n## WebGPU Limitations \u0026 Solutions\nOne key limitation of `WebGPU` is the lack of support for synchronous GPU readback. To address this, we use `AsyncGPUReadback` instead of the traditional `ReadPixels` method:\n\n```csharp\nprivate void ReadbackTexture(Texture texture, Action\u003cTexture2D\u003e callback)\n{\n    AsyncGPUReadback.Request(texture, 0, readback =\u003e\n    {\n        if (readback.hasError)\n        {\n            Debug.LogError(\"GPU readback error detected.\");\n            return;\n        }\n        Texture2D texture2D = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false);\n        texture2D.LoadRawTextureData(readback.GetData\u003cbyte\u003e());\n        texture2D.Apply();\n        callback(texture2D);\n    });\n}\n```\nThis asynchronous approach ensures compatibility with WebGPU while maintaining efficient GPU-to-CPU data transfer. You can see an example implementation [here](https://github.com/aniketrajnish/Unity-WebGPU-PBR-Maps-Generator/blob/cc98607c42f736d329499dfdf8d609924396dd7d/src/PBR%20Maps%20Generator/Assets/Scripts/Conversion/Maps/Normal.cs#L49).\n\n## Installation\n- Clone the repository or download the `.unitypackage` from [here](https://github.com/aniketrajnish/PBR-Maps-Generator/releases/tag/v001).\n- Open/Import the project in `Unity 2023.3` or later.\n- If you're using the `.unitypackage`, make sure to create a project using the `Built-In Render Pipeline` and import `TextMeshPro`.\n- Make sure to enable the `WebGPU` backend to take advantage of GPU acceleration. Instructions [here](https://discussions.unity.com/t/early-access-to-the-new-webgpu-backend-in-unity-2023-3/933493).\n\n## Contributing\nPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Currently working on-\n- Better algorithms for generating the maps that give better results. \n- Incorporating deep learning models to estimate the height information from the color information instead of just doing a greyscale conversion.\n\n## License\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faniketrajnish%2Funity-webgpu-pbr-maps-generator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faniketrajnish%2Funity-webgpu-pbr-maps-generator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faniketrajnish%2Funity-webgpu-pbr-maps-generator/lists"}