commit bca297ef3f698443307d97629abf6b0bd2b1f88c
parent 27fae4497819342e2bb2c16cbccf0e5002b6782a
Author: Matsuda Kenji <info@mtkn.jp>
Date: Sat, 16 Nov 2024 08:47:54 +0900
mandelbrot
Diffstat:
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")