tofu

Making something with OpenGL in Go
Log | Files | Refs

commit bca297ef3f698443307d97629abf6b0bd2b1f88c
parent 27fae4497819342e2bb2c16cbccf0e5002b6782a
Author: Matsuda Kenji <info@mtkn.jp>
Date:   Sat, 16 Nov 2024 08:47:54 +0900

mandelbrot

Diffstat:
Acmd/mandelbrot/fragment.glsl | 40++++++++++++++++++++++++++++++++++++++++
Acmd/mandelbrot/main.go | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/mandelbrot/vertex.glsl | 10++++++++++
Acmd/sample/container2.png | 0
Acmd/sample/container2_specular.png | 0
Mcmd/sample/fragment.glsl | 35++++++++++++++++++++++++-----------
Mcmd/sample/main.go | 20+++++++++++++-------
Acmd/sample/matrix.jpg | 0
Mshader.go | 9+++++++++
Mtofu.go | 51++++++++++++++++++++++++++++++++++++++-------------
10 files changed, 263 insertions(+), 31 deletions(-)

diff --git a/cmd/mandelbrot/fragment.glsl b/cmd/mandelbrot/fragment.glsl @@ -0,0 +1,40 @@ +#version 330 core +in vec2 fpos; +out vec4 fcol; + +vec4 color(int, int); + +void main() { + float x, y; + float xx, yy; + + const int N = 1000; + const float T = 2; + + x = 0; y = 0; + int i; + for (i = 0; i < N; i++) { + // z_n+1 = z_n^2 + c, z_0 = 0 + // (x + yi) * (x + yi) = x*x - y*y + (y*x + x*y)*i + xx = x; + yy = y; + x = xx*xx - yy*yy + fpos.x; + y = 2*xx*yy + fpos.y; + if (x*x + y*y > T) { + fcol = color(i, N); + return; + } + } + fcol = color(i, N); +} + +vec4 color(int i, int N) { + if (i < N / 3) { + return vec4(0, float(i)/N, 1-float(i)/N, 1); + } else if (i < N / 2) { + return vec4(float(i)/N, 1-float(i)/N, 0, 1); + } else { + return vec4(1-float(i)/N, 0, 0, 1); + } +} + diff --git a/cmd/mandelbrot/main.go b/cmd/mandelbrot/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "image/color" + "log" + "path/filepath" + "runtime" + "sync" + + "git.mtkn.jp/tofu" +) + +type App struct { + m *sync.Mutex + program *tofu.Program + cursor tofu.Cursor + cursorChan chan tofu.Cursor + scrollChan chan tofu.Scroll + magnitude float32 + center tofu.Vec2 +} + +func (app *App) Update() error { + app.m.Lock() + defer app.m.Unlock() + const delta = 0.01 + if tofu.IsKeyPressed(tofu.KeyQ) { + return tofu.Termination + } + if tofu.IsKeyPressed(tofu.KeyRight) { + app.center[0] += delta * app.magnitude + } + if tofu.IsKeyPressed(tofu.KeyLeft) { + app.center[0] -= delta * app.magnitude + } + if tofu.IsKeyPressed(tofu.KeyUp) { + app.center[1] += delta * app.magnitude + } + if tofu.IsKeyPressed(tofu.KeyDown) { + app.center[1] -= delta * app.magnitude + } + app.program.Use() + app.program.SetFloat32("magnitude", app.magnitude) + app.program.SetVec2("center", app.center) + object.Draw() + return nil +} + +func (app *App) CursorChan() chan<- tofu.Cursor { + return app.cursorChan +} + +func (app *App) ScrollChan() chan<- tofu.Scroll { + return app.scrollChan +} + +func main() { + var err error + app := &App{ + m: new(sync.Mutex), + cursorChan: make(chan tofu.Cursor), + scrollChan: make(chan tofu.Scroll), + magnitude: 1, + } + go func() { + for c := range app.cursorChan { + app.m.Lock() + app.cursor = c + app.m.Unlock() + } + }() + go func() { + const speed = 0.9 + for s := range app.scrollChan { + app.m.Lock() + if s.Dy > 0 { + app.magnitude *= speed + } else { + app.magnitude /= speed + } + app.m.Unlock() + } + }() + + tofu.SetWindowSize(800, 600) + tofu.SetWindowTitle("mandelbrot") + if err = tofu.Init(); err != nil { + log.Fatal(err) + } + object.Load() + _, f, _, ok := runtime.Caller(0) + if !ok { + log.Fatalf("unable to get source file information") + } + vpath := filepath.Join(filepath.Dir(f), "vertex.glsl") + fpath := filepath.Join(filepath.Dir(f), "fragment.glsl") + app.program, err = tofu.NewProgram(vpath, fpath) + if err != nil { + log.Fatalf("create shader program: %v", err) + } + if err := tofu.Run(app); err != nil { + log.Fatal(err) + } +} + +var object = tofu.Object{ + Vertices: []tofu.Vec3{ + tofu.Vec3{1, 1, 0}, + tofu.Vec3{1, -1, 0}, + tofu.Vec3{-1, -1, 0}, + tofu.Vec3{-1, 1, 0}, + }, + Colors: []color.Color{ + color.RGBA{}, + color.RGBA{}, + color.RGBA{}, + color.RGBA{}, + }, + Normals: []tofu.Vec3{ + {0, 0, 0}, + }, + TexCoords: []tofu.Vec2{ + {0, 0}, + }, + Faces: []tofu.Face{ + {V: [3]int{0, 1, 2}}, + {V: [3]int{0, 2, 3}}, + }, +} diff --git a/cmd/mandelbrot/vertex.glsl b/cmd/mandelbrot/vertex.glsl @@ -0,0 +1,10 @@ +#version 330 core +layout (location = 0) in vec3 pos; +uniform float magnitude; +uniform vec2 center; +out vec2 fpos; +void main() { + gl_Position = vec4(pos, 1.0); + fpos = (pos.xy * 2 * magnitude) + center; +} + diff --git a/cmd/sample/container2.png b/cmd/sample/container2.png Binary files differ. diff --git a/cmd/sample/container2_specular.png b/cmd/sample/container2_specular.png Binary files differ. diff --git a/cmd/sample/fragment.glsl b/cmd/sample/fragment.glsl @@ -7,10 +7,14 @@ struct Material { }; struct Light { - vec3 vec; + vec3 pos; + vec3 dir; vec3 ambient; vec3 diffuse; vec3 specular; + + float Kc, Kl, Kq; + float cutOff; }; in vec3 fnormal; @@ -30,27 +34,36 @@ void main() { vec3 lightDir, diffuse; vec3 viewDir, specular; vec3 reflectDir; - float diff, spec; + float diff, spec, dist, attenuation; + float theta; fcol = vec4(0, 0, 0, 1); + diffuse = vec3(0, 0, 0); + specular = vec3(0, 0, 0); + + dist = length(light.pos - fpos); + attenuation = 1 / (light.Kc + light.Kl * dist + light.Kq * dist * dist); ambient = vec3(texture(material.diffuse, texPos)) * light.ambient; - lightDir = normalize(light.vec - fpos); - diff = max(dot(fnormal, lightDir), 0.0); - diffuse = diff * vec3(texture(material.diffuse, texPos)) * light.diffuse; + lightDir = normalize(light.pos - fpos); + theta = dot(lightDir, normalize(-light.dir)); + if (theta > light.cutOff) { + diff = max(dot(fnormal, lightDir), 0.0); + diffuse = diff * vec3(texture(material.diffuse, texPos)) * light.diffuse; - viewDir = normalize(camPos - fpos); - reflectDir = reflect(-lightDir, fnormal); - spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shiness); - specular = vec3(texture(material.specular, texPos)) * spec * light.specular; + viewDir = normalize(camPos - fpos); + reflectDir = reflect(-lightDir, fnormal); + spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shiness); + specular = vec3(texture(material.specular, texPos)) * spec * light.specular; + } - fcol += vec4(ambient + diffuse + specular, 0); + fcol += vec4(ambient + diffuse + specular, 0) * attenuation; ambient = vec3(texture(material.diffuse, texPos)) * sun.ambient; - lightDir = normalize(-sun.vec); + lightDir = normalize(-sun.dir); diff = max(dot(fnormal, lightDir), 0.0); diffuse = diff * vec3(texture(material.diffuse, texPos)) * sun.diffuse; diff --git a/cmd/sample/main.go b/cmd/sample/main.go @@ -128,6 +128,7 @@ var object = tofu.Object{ } var cubePositions = []tofu.Vec3{ + {0, 0, 0}, {4.24, 0.94, -1.94}, {0.79, 2.40, 2.87}, {-0.64, -1.68, 2.79}, @@ -170,10 +171,10 @@ func (app *App) Update() error { lightCol := tofu.Vec3{1, 1, 1} lightModel := tofu.Rotate(now, tofu.Vec3{0, 1, 0}). - Mul(tofu.Translate(tofu.Vec3{5, 4, 0})). + Mul(tofu.Translate(tofu.Vec3{5, 4.5, 0})). Mul(tofu.Scale(0.3)) lightPos := tofu.Rotate3(now, tofu.Vec3{0, 1, 0}). - MulV(tofu.Vec3{5, 4, 0}) + MulV(tofu.Vec3{5, 4.5, 0}) app.program.Use() app.program.SetMat4("view", view) @@ -182,14 +183,19 @@ func (app *App) Update() error { app.program.SetTexture("material.diffuse", app.texture) app.program.SetTexture("material.specular", app.specularMap) app.program.SetFloat32("material.shiness", 32) - app.program.SetVec3("sun.vec", sunDir) - app.program.SetVec3("sun.ambient", sunCol.MulF(0.8)) - app.program.SetVec3("sun.diffuse", sunCol.MulF(0.9)) + app.program.SetVec3("sun.dir", sunDir) + app.program.SetVec3("sun.ambient", sunCol.MulF(0.5)) + app.program.SetVec3("sun.diffuse", sunCol.MulF(0.3)) app.program.SetVec3("sun.specular", sunCol) - app.program.SetVec3("light.vec", lightPos) + app.program.SetVec3("light.pos", lightPos) + app.program.SetVec3("light.dir", tofu.Vec3{0, 0, 0}.Sub(lightPos)) + app.program.SetFloat32("light.cutOff", float32(math.Cos(12.5*math.Pi/180))) app.program.SetVec3("light.ambient", lightCol.MulF(0.1)) - app.program.SetVec3("light.diffuse", lightCol.MulF(0.6)) + app.program.SetVec3("light.diffuse", lightCol.MulF(0.9)) app.program.SetVec3("light.specular", lightCol) + app.program.SetFloat32("light.Kc", 1.0) + app.program.SetFloat32("light.Kl", 0.0009) + app.program.SetFloat32("light.Kq", 0.00032) for _, p := range cubePositions { model := tofu.Translate(p.Inverse()) // Mul(tofu.Rotate(20*float32(i)+now, tofu.Vec3{math.Sqrt2 / 2, -math.Sqrt2 / 2, 0})) diff --git a/cmd/sample/matrix.jpg b/cmd/sample/matrix.jpg Binary files differ. diff --git a/shader.go b/shader.go @@ -98,6 +98,15 @@ func (p *Program) SetFloat32(name string, val float32) error { return nil } +func (p *Program) SetVec2(name string, val Vec2) error { + l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00")) + if l == -1 { + return fmt.Errorf("uniform %s not found", name) + } + gl.Uniform2fv(l, 1, &val[0]) + return nil +} + func (p *Program) SetVec3(name string, val Vec3) error { l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00")) if l == -1 { diff --git a/tofu.go b/tofu.go @@ -12,13 +12,40 @@ import ( type App interface { Update() error - CursorChan() chan<- Cursor } type Cursor struct { X, Y int } +type CursorApp interface { + App + CursorChan() chan<- Cursor +} + +func watchCursor(c chan<- Cursor) { + callback := func(w *glfw.Window, x, y float64) { + c <- Cursor{X: int(x), Y: int(y)} + } + window.SetCursorPosCallback(callback) +} + +type Scroll struct { + Dx, Dy float32 +} + +type ScrollApp interface { + App + ScrollChan() chan<- Scroll +} + +func watchScroll(c chan<- Scroll) { + callback := func(w *glfw.Window, dx, dy float64) { + c <- Scroll{Dx: float32(dx), Dy: float32(dy)} + } + window.SetScrollCallback(callback) +} + var ( window *glfw.Window winW, winH int = 800, 600 @@ -55,10 +82,17 @@ func Init() error { } func Run(app App) error { - if c := app.CursorChan(); c != nil { + if app, ok := app.(CursorApp); ok { + c := app.CursorChan() watchCursor(c) defer close(c) } + if app, ok := app.(ScrollApp); ok { + c := app.ScrollChan() + watchScroll(c) + defer close(c) + } + for !window.ShouldClose() { gl.ClearColor(1.0, 1.0, 0.918, 1.0) gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) @@ -73,13 +107,6 @@ func Run(app App) error { return nil } -func watchCursor(c chan<- Cursor) { - callback := func(w *glfw.Window, x, y float64) { - c <- Cursor{X: int(x), Y: int(y)} - } - window.SetCursorPosCallback(callback) -} - func messageCallback(source, gltype, id, severity uint32, length int32, message string, userProgram unsafe.Pointer) { log.Printf("debug: type = 0x%x, severity = 0x%x, message = %s", gltype, severity, message) } @@ -91,10 +118,6 @@ func framebufferSizeCallback(w *glfw.Window, width int, height int) { func SetFramebufferSizeCallback(f glfw.FramebufferSizeCallback) { } -func SetKeyCallback(f func(*glfw.Window)) { - keyCallback = f -} - func SetWindowSize(w, h int) { winW, winH = w, h } @@ -136,6 +159,8 @@ const ( KeyLeftShift = Key(glfw.KeyLeftShift) KeyUp = Key(glfw.KeyUp) KeyDown = Key(glfw.KeyDown) + KeyRight = Key(glfw.KeyRight) + KeyLeft = Key(glfw.KeyLeft) ) var Termination error = errors.New("Termination")