github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/render/shade.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "image" 6 "image/color" 7 "image/png" 8 "math" 9 "os" 10 "time" 11 ) 12 13 func main() { 14 save("shade.png", Shade) 15 } 16 17 const ( 18 ZOOM = 0.8 19 VOLUME_STEPS = 20 20 ITERATIONS = 17 21 22 FORMULA_PARAM = 0.53 23 BRIGHTNESS = 0.0015 24 DISTANCE_FADING = 0.730 25 STEP_SIZE = 0.1 26 ) 27 28 func Shade(p, uv, res V2) Color { 29 uv.Y *= res.Y / res.X 30 31 dir := V3{uv.X * ZOOM, uv.Y * ZOOM, 1} 32 from := V3{0.98, 0.5122, 0.53} 33 34 s := float(0.1) 35 fade := float(1) 36 37 v := V3{} 38 for r := 0; r < VOLUME_STEPS; r++ { 39 p := from.Add(dir.Scale(s * 0.5)) 40 var pa, a float 41 for i := 0; i < ITERATIONS; i++ { 42 p = p.Abs().Scale(1.0 / p.Len2()).Offset(-FORMULA_PARAM) 43 a += abs(p.Len() - pa) 44 } 45 a *= a * a 46 v = v.Offset(fade) 47 v = v.Add(V3{s, s * s, s * s * s * s}.Scale(a * fade * BRIGHTNESS)) 48 49 fade *= DISTANCE_FADING 50 s += STEP_SIZE 51 } 52 53 v = v.Scale(0.01) 54 return C3(v.X, v.Y, v.Z) 55 } 56 57 type Shader func(p, uv V2, res V2) Color 58 59 func Render(m *image.RGBA, shader Shader) { 60 size := m.Bounds().Size() 61 res := V2{float(size.X), float(size.Y)} 62 for y := 0; y < size.Y; y++ { 63 for x := 0; x < size.X; x++ { 64 p := V2{float(x), float(y)} 65 uv := V2{float(x) / res.X, float(y) / res.Y} 66 color := shader(p, uv, res) 67 m.SetRGBA(x, y, color.RGBA()) 68 } 69 } 70 } 71 72 func save(name string, shader Shader) { 73 fmt.Println("RENDER ", name) 74 start := time.Now() 75 m := image.NewRGBA(image.Rect(0, 0, 640, 480)) 76 Render(m, shader) 77 stop := time.Now() 78 fmt.Println("DONE ", stop.Sub(start)) 79 80 f, err := os.Create(name) 81 check(err) 82 check(png.Encode(f, m)) 83 check(f.Close()) 84 } 85 86 func check(err error) { 87 if err != nil { 88 panic(err) 89 } 90 } 91 92 // vector stuff 93 94 type V2 struct{ X, Y float } 95 type V3 struct{ X, Y, Z float } 96 97 func (v V2) Abs() V2 { return V2{abs(v.X), abs(v.Y)} } 98 func (v V3) Abs() V3 { return V3{abs(v.X), abs(v.Y), abs(v.Z)} } 99 100 func (v V2) Len2() float { return v.X*v.X + v.Y*v.Y } 101 func (v V3) Len2() float { return v.X*v.X + v.Y*v.Y + v.Z*v.Z } 102 103 func (v V2) Len() float { return sqrt(v.Len2()) } 104 func (v V3) Len() float { return sqrt(v.Len2()) } 105 106 func (v V2) LenV() V2 { 107 d := v.Len() 108 return V2{d, d} 109 } 110 func (v V3) LenV() V3 { 111 d := v.Len() 112 return V3{d, d, d} 113 } 114 115 func (v V2) Scale(s float) V2 { return V2{v.X * s, v.Y * s} } 116 func (v V3) Scale(s float) V3 { return V3{v.X * s, v.Y * s, v.Z * s} } 117 118 func (a V2) Add(b V2) V2 { return V2{a.X + b.X, a.Y + b.Y} } 119 func (a V3) Add(b V3) V3 { return V3{a.X + b.X, a.Y + b.Y, a.Z + b.Z} } 120 121 func (a V2) Offset(s float) V2 { return V2{a.X + s, a.Y + s} } 122 func (a V3) Offset(s float) V3 { return V3{a.X + s, a.Y + s, a.Z + s} } 123 124 // general math 125 126 const ( 127 Pi = math.Pi 128 Tau = 2 * math.Pi 129 ) 130 131 type float float64 132 133 func abs(v float) float { 134 if v < 0 { 135 return -v 136 } 137 return v 138 } 139 140 func cos(v float) float { return float(math.Cos(float64(v))) } 141 func sin(v float) float { return float(math.Sin(float64(v))) } 142 func sqrt(v float) float { return float(math.Sqrt(float64(v))) } 143 144 func lerp(a, b, p float) float { return (1.0-p)*a + p*b } 145 func u8(v float) uint8 { 146 x := int(v * 256) 147 if x >= 256 { 148 return 255 149 } else if x < 0 { 150 return 0 151 } 152 return uint8(x) 153 } 154 155 func mod1(v float) float { 156 vi := float(math.Trunc(float64(v))) 157 if vi < 0 { 158 return v - vi + 1 159 } 160 return v - vi 161 } 162 163 func clamp1(v float) float { 164 if v > 1 { 165 return 1 166 } else if v < 0 { 167 return 0 168 } 169 return v 170 } 171 172 // Color utilities 173 type Color struct{ R, G, B, A float } 174 175 func (c Color) RGBA() color.RGBA { 176 return color.RGBA{u8(c.R), u8(c.G), u8(c.B), u8(c.A)} 177 } 178 179 func G(gray float) Color { return Color{gray, gray, gray, 1} } 180 func C3(R, G, B float) Color { return Color{R, G, B, 1} } 181 func C4(R, G, B, A float) Color { return Color{R, G, B, A} } 182 183 func hue(p, q, h float) float { 184 if h < 0.0 { 185 h += 1.0 186 } else if h > 1.0 { 187 h -= 1.0 188 } 189 if h < 1.0/6.0 { 190 return p + (q-p)*6.0*h 191 } else if h < 1.0/2.0 { 192 return q 193 } else if h < 2.0/3.0 { 194 return p + (q-p)*6.0*(2.0/3.0-h) 195 } 196 return p 197 } 198 199 func HSL(H, S, L float) Color { 200 S, L = clamp1(S), clamp1(L) 201 if S <= 0 { 202 return C3(L, L, L) 203 } 204 H = mod1(H) 205 206 var q float 207 if L < 0.5 { 208 q = L * (1 + S) 209 } else { 210 q = L + S - S*L 211 } 212 p := 2*L - q 213 return C3(hue(p, q, H+1.0/3.0), hue(p, q, H), hue(p, q, H-1.0/3.0)) 214 } 215 216 func HSLA(H, S, L, A float) Color { 217 c := HSL(H, S, L) 218 c.A = A 219 return c 220 }