{"id":21141584,"url":"https://github.com/bjornbytes/shh","last_synced_at":"2026-02-08T22:32:14.305Z","repository":{"id":215887863,"uuid":"703200505","full_name":"bjornbytes/shh","owner":"bjornbytes","description":"Spherical Harmonics Helpers","archived":false,"fork":false,"pushed_at":"2024-09-06T22:49:37.000Z","size":5771,"stargazers_count":3,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-09-07T08:15:37.561Z","etag":null,"topics":["spherical-harmonics"],"latest_commit_sha":null,"homepage":"","language":"Lua","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/bjornbytes.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":"2023-10-10T19:32:13.000Z","updated_at":"2024-09-06T22:49:40.000Z","dependencies_parsed_at":null,"dependency_job_id":"6c4a46d9-c10f-40ac-b176-dd2c9e01f84f","html_url":"https://github.com/bjornbytes/shh","commit_stats":null,"previous_names":["bjornbytes/shh"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bjornbytes%2Fshh","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bjornbytes%2Fshh/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bjornbytes%2Fshh/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bjornbytes%2Fshh/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bjornbytes","download_url":"https://codeload.github.com/bjornbytes/shh/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225486629,"owners_count":17481935,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["spherical-harmonics"],"created_at":"2024-11-20T07:32:04.723Z","updated_at":"2026-02-08T22:32:09.286Z","avatar_url":"https://github.com/bjornbytes.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"SHH 🤫\n===\n\nSHH (**S**pherical **H**armonics **H**elpers) is a Lua/[LÖVR](https://lovr.org) library for\ncomputing spherical harmonics and using them for lighting.  It uses compute shaders to generate\nspherical harmonics on the GPU at realtime speeds.\n\nSpherical What Now?\n---\n\nSpherical harmonics basically store a \"ball of light\" using some fancy math and a very small amount\nof data.  They're similar to a skybox, but more blurry and only take up about 100 bytes!  They're a\nquick, cheap way to add soft diffuse lighting to objects so they look like they belong in a scene.\n\n[Here](https://martinoshelf.neocities.org/spherical-harmonics-lovr) is a blog post that goes into\nmore of the math behind spherical harmonics, with great diagrams and sample code!\n\nExample\n---\n\n```lua\nlocal shh = require 'shh'\n\nfunction lovr.load()\n  skybox = lovr.graphics.newTexture('sky.hdr', {\n    usage = { 'storage', 'sample' }\n  })\n\n  SH = shh.new(skybox)\n\n  model = lovr.graphics.newModel('armordillo.glb')\nend\n\nfunction lovr.draw(pass)\n  pass:skybox(skybox)\n  shh.setShader(pass, SH)\n  pass:draw(model)\nend\n```\n\n![Ball Lit By Skybox](assets/screenshot.jpg)\n\nUsage\n---\n\nFirst, copy the `shh.lua` file to your project and require it.\n\nTo create a new spherical harmonics object, use `SH = shh.new(arg)`.  The argument can be:\n\n- `nil` - Create an empty object (all coefficients will be zero).\n- `table` - A table of 27 numbers or a table of 9 tables (each with 3 numbers) containing the raw\n  coefficients.\n- `Texture` - A Texture to compute the coefficients from.  It can be a cubemap texture or an\n  equirectangular texture (2d spherical image with 2:1 aspect ratio).\n\nA SH object is simply a table of 9 basis vectors, so you can access `SH[x][y]` to get the raw\ncoefficient values (where x is 1-9 and y is 1-3).\n\nSpherical harmonics objects have the following methods:\n\n- `SH:evaluate(dx, dy, dz)` - Get the color at a given direction.  The direction can 3 numbers or a\n  `vec3` object, and should be normalized.  This basically blends all the coefficients together\n  based on the direction to get a final color value.\n- `SH:addAmbientLight(r, g, b)` - Add an ambient light to the coefficients.  This will be added to\n  any existing light in the SH object.\n- `SH:addDirectionalLight(dx, dy, dz, r, g, b)` - Add a directional light to the coefficients.  This\n  will be added to any existing light in the SH object.  The direction vector should be the\n  direction *towards* the light.  Note that due to math reasons, a small amount of light will \"leak\"\n  in the opposite direction of the light.\n- `SH:add(other)` - Add another spherical harmonics object to this one, essentially summing their\n  light together.\n- `SH:lerp(other, t)` - Blend this spherical harmonics object with another one.  `t` is a value from\n  zero to one, where 0 will keep `SH` the same and 1 will set `SH` equal to `other`.\n- `SH:scale(x)` - Multiply all coefficients by `x`, which will have brightening/darkening effect.\n\nLighting\n---\n\nThere are a few ways to use spherical harmonics for lighting.\n\nSHH provides a convenience function `shh.setShader(pass, SH)` which will set a shader on `pass` that\nwill light objects using the `SH` object.\n\nHowever, spherical harmonics lighting can be integrated into your own shaders and combined with\ndirect lighting, PBR materials, etc.  To make this easier, SHH has a `shader.glsl` file that can be\nincluded in custom shaders.  It defines an `evaluateSH` function that takes a spherical harmonics\nbasis and a direction vector, and returns a color:\n\n```glsl\n#include \"shh/shader.glsl\"\n\nlayout(set = 2, binding = 0) uniform SH { vec3 sh[9]; };\n\nvec4 lovrmain() {\n  vec3 color = evaluateSH(sh, normalize(Normal)) / PI;\n  return vec4(color, 1.);\n}\n```\n\n**Note** that when doing lighting, you probably want to divide the result from `evaluateSH` by `PI`.\nThis applies the Lambertian BRDF which gets the values in a more appropriate range.\n\nAdvanced Compute Shader API\n---\n\nTo generate spherical harmonics from a Texture using a compute shader, use `shh.compute`:\n\n```lua\nshh.compute(pass, texture, buffer, bufferOffset)\n```\n\nExample:\n\n```lua\nlocal pass = lovr.graphics.newPass()\nlocal skybox = lovr.graphics.newTexture('skybox.hdr', { usage = 'storage' })\nlocal buffer = lovr.graphics.newBuffer('vec4', 9)\nshh.compute(pass, skybox, buffer)\nlovr.graphics.submit(pass)\nSH = shh.new(buffer:getData())\n\n-- or, compute SH dynamically every frame:\nfunction lovr.draw(pass)\n  shh.compute(pass, skybox, buffer)\n  shh.setShader(pass, buffer)\n  pass:draw(model)\nend\n```\n\nThis is a lower-level method that will run a compute shader on `pass` that computes spherical\nharmonics coefficients from `texture` and writes them to `buffer` (at offset `bufferOffset`, which\ndefaults to zero).  It can be used to compute spherical harmonics for many textures on the GPU in\nparallel.\n\nLike `shh.new`, the texture should be a cubemap or an equirectangular texture.  It must have the\n`storage` usage.  Currently its format must be `rgba8`, `rgba16f`, `rgba32f`, or `rg11b10f`.\n\n\u003e Tip: You can pass a texture view to this function to compute spherical harmonics from a smaller\n\u003e mipmap level of a cubemap.  This usually gives roughly the same results but is much faster.\n\nThe buffer should use the `vec4` format or the `vec3` format with the `std140` layout.  144 bytes\nwill be written to the buffer.\n\nThe buffer can be used directly in a shader, or, after submitting the pass, the coefficients can be\nread back to the CPU using `Buffer:getData`.  Sending the buffer directly to a shader will avoid any\ncostly readbacks and keep all the data on the GPU.\n\nLicense\n---\n\nMIT, see the [LICENSE](./LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbjornbytes%2Fshh","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbjornbytes%2Fshh","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbjornbytes%2Fshh/lists"}