Преглед изворни кода

README; LICENSE; Screenshot;

bjorn пре 1 година
родитељ
комит
5c6f05b62e
6 измењених фајлова са 188 додато и 0 уклоњено
  1. 19 0
      LICENSE
  2. 146 0
      README.md
  3. 3 0
      assets/README.md
  4. BIN
      assets/industrial_workshop_foundry_2k.hdr
  5. BIN
      assets/screenshot.jpg
  6. 20 0
      main.lua

+ 19 - 0
LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2024 Bjorn Swenson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 146 - 0
README.md

@@ -0,0 +1,146 @@
+SHH 🤫
+===
+
+SHH (**S**pherical **H**armonics **H**elpers) is a Lua/[LÖVR](https://lovr.org) library for
+computing spherical harmonics and using them for lighting.  It uses compute shaders to generate
+spherical harmonics on the GPU at realtime speeds.
+
+Spherical What Now?
+---
+
+Spherical harmonics basically store a "ball of light" using some fancy math and a very small amount
+of data.  They're similar to a skybox, but more blurry and only take up about 100 bytes!  They're a
+quick, cheap way to add soft diffuse lighting to objects so they look like they belong in a scene.
+
+Example
+---
+
+```lua
+local shh = require 'shh'
+
+function lovr.load()
+  skybox = lovr.graphics.newTexture('sky.hdr', {
+    usage = { 'storage', 'sample' }
+  })
+
+  SH = shh.new(skybox)
+end
+
+function lovr.draw(pass)
+  pass:skybox(skybox)
+  shh.setShader(pass, SH)
+  pass:sphere(0, 1.7, -2)
+end
+```
+
+![Ball Lit By Skybox](assets/screenshot.jpg)
+
+Usage
+---
+
+First, copy the `shh.lua` file to your project and require it.
+
+To create a new spherical harmonics object, use `SH = shh.new(arg)`.  The argument can be:
+
+- `nil` - Create an empty object (all coefficients will be zero).
+- `table` - A table of 27 numbers or a table of 9 tables (each with 3 numbers) containing the raw
+  coefficients.
+- `Texture` - A Texture to compute the coefficients from.  It can be a cubemap texture or an
+  equirectangular texture (2d spherical image with 2:1 aspect ratio).
+
+A SH object is simply a table of 9 basis vectors, so you can access `SH[x][y]` to get the raw
+coefficient values (where x is 1-9 and y is 1-3).
+
+Spherical harmonics objects have the following methods:
+
+- `SH:evaluate(dx, dy, dz)` - Get the color at a given direction.  The direction can 3 numbers or a
+  `vec3` object, and should be normalized.  This basically blends all the coefficients together
+  based on the direction to get a final color value.
+- `SH:addAmbientLight(r, g, b)` - Add an ambient light to the coefficients.  This will be added to
+  any existing light in the SH object.
+- `SH:addDirectionalLight(dx, dy, dz, r, g, b)` - Add a directional light to the coefficients.  This
+  will be added to any existing light in the SH object.  The direction vector should be the
+  direction *towards* the light.  Note that due to math reasons, a small amount of light will "leak"
+  in the opposite direction of the light.
+- `SH:add(other)` - Add another spherical harmonics object to this one, essentially summing their
+  light together.
+- `SH:lerp(other, t)` - Blend this spherical harmonics object with another one.  `t` is a value from
+  zero to one, where 0 will keep `SH` the same and 1 will set `SH` equal to `other`.
+- `SH:scale(x)` - Multiply all coefficients by `x`, which will have brightening/darkening effect.
+
+Lighting
+---
+
+There are a few ways to use spherical harmonics for lighting.
+
+SHH provides a convenience function `shh.setShader(pass, SH)` which will set a shader on `pass` that
+will light objects using the `SH` object.
+
+However, spherical harmonics lighting can be integrated into your own shaders and combined with
+direct lighting, PBR materials, etc.  To make this easier, SHH has a `shader.glsl` file that can be
+included in custom shaders.  It defines an `evaluateSH` function that takes a spherical harmonics
+basis and a direction vector, and returns a color:
+
+```glsl
+#include "shh/shader.glsl"
+
+layout(set = 2, binding = 0) uniform SH { sh[9]; };
+
+vec4 lovrmain() {
+  vec3 color = evaluateSH(sh, normalize(Normal)) / PI;
+  return vec4(color, 1.);
+}
+```
+
+**Note** that when doing lighting, you probably want to divide the result from `evaluateSH` by `PI`.
+This applies the Lambertian BRDF which gets the values in a more appropriate range.
+
+Advanced Compute Shader API
+---
+
+To generate spherical harmonics from a Texture using a compute shader, use `shh.compute`:
+
+```lua
+shh.compute(pass, texture, buffer, bufferOffset)
+```
+
+Example:
+
+```lua
+local pass = lovr.graphics.newPass()
+local skybox = lovr.graphics.newTexture('skybox.hdr', { usage = 'storage' })
+local buffer = lovr.graphics.newBuffer('vec4', 9)
+shh.compute(pass, skybox, buffer)
+lovr.graphics.submit(pass)
+SH = shh.new(buffer:getData())
+
+-- or, compute SH dynamically every frame:
+function lovr.draw(pass)
+  shh.compute(pass, skybox, buffer)
+  shh.setShader(pass, buffer)
+  pass:draw(model)
+end
+```
+
+This is a lower-level method that will run a compute shader on `pass` that computes spherical
+harmonics coefficients from `texture` and writes them to `buffer` (at offset `bufferOffset`, which
+defaults to zero).  It can be used to compute spherical harmonics for many textures on the GPU in
+parallel.
+
+Like `shh.new`, the texture should be a cubemap or an equirectangular texture.  It must have the
+`storage` usage.  Currently its format must be `rgba8`, `rgba16f`, `rgba32f`, or `rg11b10f`.
+
+> Tip: You can pass a texture view to this function to compute spherical harmonics from a smaller
+> mipmap level of a cubemap.  This usually gives roughly the same results but is much faster.
+
+The buffer should use the `vec4` format or the `vec3` format with the `std140` layout.  144 bytes
+will be written to the buffer.
+
+The buffer can be used directly in a shader, or, after submitting the pass, the coefficients can be
+read back to the CPU using `Buffer:getData`.  Sending the buffer directly to a shader will avoid any
+costly readbacks and keep all the data on the GPU.
+
+License
+---
+
+MIT, see the [LICENSE](./LICENSE) file for details.

+ 3 - 0
assets/README.md

@@ -0,0 +1,3 @@
+CC0 Industrial Workshop Foundry environment map from Polyhaven:
+
+https://polyhaven.com/a/industrial_workshop_foundry

BIN
assets/industrial_workshop_foundry_2k.hdr


BIN
assets/screenshot.jpg


+ 20 - 0
main.lua

@@ -0,0 +1,20 @@
+local shh = require 'shh'
+
+function lovr.load()
+  skybox = lovr.graphics.newTexture('assets/industrial_workshop_foundry_2k.hdr', {
+    usage = { 'storage', 'sample' }
+  })
+
+  colors = shh.new(skybox)
+
+  print('Coefficients:')
+  for i, color in ipairs(colors) do
+    print(i, ('% 6f % 6f % 6f'):format(unpack(color)))
+  end
+end
+
+function lovr.draw(pass)
+  pass:skybox(skybox)
+  shh.setShader(pass, colors)
+  pass:sphere(0, 1.7, -2)
+end