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  }