golang game development study notes - create a freely explore the world of 3D

This text written in golang game development study notes - draw a square with golang change color over time after, interested can go to the article to know some basics In this article, we will create a very simple (only three cubes) but free to explore the world of 3D

1. References

learnOpenGl the Chinese translation, the use of C++implementation.
go-gl example sample code of go-gl

2. Basic concepts

  1. Related mathematical concepts such as matrices , vectors , etc., are interested can find relevant information on the Internet
  2. Textures can be understood as the model we created maps
  3. Texture coordinates , in the range (0,0) to (1,1) between the texture image corresponding to the top right and bottom left
  4. Texture surround mode , the texture coordinates after processing beyond the scope of what to do, such as a duplicate image texture, color and the like other filler
  5. Texture filtering , texture pixel does not correspond with the coordinates, we need to specify the mapping mode
  6. Partial space , will be understood that a reference point is chosen to create the object, all other vertices are the object relative to the reference point are arranged, the coordinates of the vertices is called a local coordinate space configuration is referred to as the partial space
  7. World Space subject to matrix operations will object moves into world space after, this is better understood, can be understood as the scene of the game, create objects
  8. Viewing space , as the name implies, seen from the viewer's perspective of the world, the same matrix operation by the world space coordinates into coordinates seen by the viewer
  9. Clip space , limited by the size of the window, we could see the whole world of space, the need for world crop space, reserved part of the window can be displayed

So we need three matrices, the first one will be in charge of the local coordinate ( local) into world coordinates (responsible for moving objects) named as modelsecond in charge of the world coordinates into the coordinates we have seen from the viewer's point of view, named view, we can see a third of the world's crop, is displayed on the window, the name projection, that is, a vertex coordinate transformation involves the following

clip = projection * view * model * local

The above concepts can be learnOpenGl find a detailed explanation of the concept, just to make a summary

3. Reliance

In C++the openglsupporting the matrix operations package GLM(OpenGL Mathematics), but did not able to find the author based on golangthe GLMpackage, only to find a named mglpackage, a closer look at the source code, matrix operations required for almost all, we should note that this dependence package depends on the imagemodule, and the official imagemodule is qiang, so the best is gopathto create a manual directory golang.org\xdirectory and then githubdownloaded directly dependent on image saved to the directory where the installed imageafter running dependence
go get github.com/go-gl/mathgl/

4. Implement

1. Create a class for loading textures texture

package texture
import(
	"os"
	"image"
	"image/jpeg"
	"errors"
	"github.com/go-gl/gl/v4.1-core/gl"
	"image/draw"
)

type LocalTexture struct{
	ID uint32
	TEXTUREINDEX uint32
}

func NewLocalTexture(file string, TEXTUREINDEX uint32) *LocalTexture{
	imgFile, err := os.Open(file)
	if err != nil {
		panic(err)
	}

	img, err := jpeg.Decode(imgFile)
	if err != nil {
		panic(err)
	}
	rgba := image.NewRGBA(img.Bounds())
	if rgba.Stride != rgba.Rect.Size().X*4 {
		panic(errors.New("unsupported stride"))
	}
	draw.Draw(rgba, rgba.Bounds(), img, image.Point{0, 0}, draw.Src)
	
	var textureID uint32
	gl.GenTextures(1, &textureID)
	gl.ActiveTexture(TEXTUREINDEX)
	gl.BindTexture(gl.TEXTURE_2D, textureID)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT)
	gl.TexImage2D(
		gl.TEXTURE_2D,
		0,
		gl.RGBA,
		int32(rgba.Rect.Size().X),
		int32(rgba.Rect.Size().Y),
		0,
		gl.RGBA,
		gl.UNSIGNED_BYTE,
		gl.Ptr(rgba.Pix))
	return &LocalTexture{ ID: textureID,TEXTUREINDEX:TEXTUREINDEX}
}

func (texture *LocalTexture) Use(){
	gl.ActiveTexture(texture.TEXTUREINDEX)
	gl.BindTexture(gl.TEXTUR_2D, texture.ID)
}

In the constructor we need to pass a file path texture jpg format (call Decode method in the image / picture package format can parse different file formats, here for simplicity, only the analytical jpg format), and a parameter of type uint32, this when we use the parameter specifies a plurality of textures, the texture shader program different placeholder. In the constructor we specify texture filtering and texture zoom mode around the filter

2. Create a transformation matrix

I mentioned earlier we have to use three matrix transforming the object coordinates

model = mgl32.Translate3D(0,0,1)

This matrix will move the object along the z-axis direction, a unit
then invokes mglthe Perspectivemethod to create a perspective matrix, field size set to 45, the near plane is 0.1, the far plane is set to 100, only the object between the two planes will be rendered, it can be understood as objects away from the invisible to the position

projection := mgl32.Perspective(45, width/height, 0.1, 100.0)

Finally, we use mglthe LookAtmethod to create a view matrix, which is used to coordinate into an object coordinate our perspective of the observer, this method requires three vectors, the first of which is the observer position in world space, and the second is direction of the observer to observe, directly above the last is a vector representing the viewing direction of a viewer (through the right fork vector can be a vector and the observation direction obtained by)

position := mgl32.Vec3{0, 0, 0}
front := mgl32.Vec3{0, 0, -1}
up:= mgl32.Vec3{0, 1, 0}

target := position.Add(front)
view := mgl32.LookAtV(position,target, up)

Code positionrepresentative of the position of the observer is located (as the origin), frontthe representative direction (along the z-axis negative direction) viewed by an observer, upon behalf of a vertical direction.
At this point, we are ready to create a 3D matrix of all the space needed, after all operations are converted to these three matrix operations, to a moving object can be achieved by modifying the model matrix, perspective needs to be changed only need to modify the view matrix, zoom responsibility vision can be accomplished by modifying the projection matrix.
One problem is how to make the direction we observe with the mouse to move and change? Front know, fronton behalf of the observer observed, modifications to this vector are used here to Euler angles, can go online to find specific information, I do not really understand, directly copy the code tutorial here
last To further abstract the relevant operation, creating a camera class

package camera
import(
	"github.com/go-gl/mathgl/mgl64"
	"github.com/go-gl/mathgl/mgl32"
	"math"
)
type Direction int
const (
    FORWARD   Direction = 0 	// 摄像机移动状态:前
    BACKWARD  Direction = 1     // 后
    LEFT      Direction = 2     // 左
    RIGHT     Direction = 3     // 右
)
type LocalCamera struct{
	position    mgl32.Vec3
	front       mgl32.Vec3
	up	        mgl32.Vec3
	right		mgl32.Vec3

	wordUp      mgl32.Vec3

	yaw   float64
	pitch float64
	zoom  float32
	
	movementSpeed float32
	mouseSensitivity float32

	constrainPitch bool

}
func NewDefaultCamera() *LocalCamera{
	position := mgl32.Vec3{0, 0, 0}
	front := mgl32.Vec3{0, 0, -1}
	wordUp := mgl32.Vec3{0, 1, 0}
	yaw := float64(-90)
	pitch := float64(0)
	movementSpeed := float32(2.5)
	mouseSensitivity := float32(0.1)
	zoom := float32(45)
	constrainPitch := true
	localCamera := &LocalCamera{position:position, 
								front:front, 
								wordUp:wordUp, 
								yaw:yaw, 
								pitch:pitch, 
								movementSpeed:movementSpeed, 
								mouseSensitivity:mouseSensitivity, 
								zoom:zoom,
								constrainPitch:constrainPitch}
	localCamera.updateCameraVectors()
	return localCamera
}
//获取当前透视矩阵
func (localCamera *LocalCamera) GetProjection(width float32, height float32) *float32{
	projection := mgl32.Perspective(mgl32.DegToRad(localCamera.zoom), float32(width)/height, 0.1, 100.0)
	return &projection[0]
}
//鼠标移动回调
func (localCamera *LocalCamera) ProcessMouseMovement(xoffset float32, yoffset float32){
	xoffset *= localCamera.mouseSensitivity
	yoffset *= localCamera.mouseSensitivity

	localCamera.yaw += float64(xoffset)
	localCamera.pitch += float64(yoffset)

	// Make sure that when pitch is out of bounds, screen doesn't get flipped
	if (localCamera.constrainPitch){
		if (localCamera.pitch > 89.0){
			localCamera.pitch = 89.0
		}
		if (localCamera.pitch < -89.0){
			localCamera.pitch = -89.0
		}
	}
	localCamera.updateCameraVectors();
}
//鼠标滑动回调
func (localCamera *LocalCamera) ProcessMouseScroll(yoffset float32){
	if (localCamera.zoom >= 1.0 && localCamera.zoom <= 45.0){
		localCamera.zoom -= yoffset;
	}
	if (localCamera.zoom <= 1.0){
		localCamera.zoom = 1.0;
	}
	if (localCamera.zoom >= 45.0){
		localCamera.zoom = 45.0;
	}
}
//键盘回调
func (localCamera *LocalCamera) ProcessKeyboard(direction Direction, deltaTime float32){
	velocity := localCamera.movementSpeed * deltaTime;
	if (direction == FORWARD){
		localCamera.position = localCamera.position.Add(localCamera.front.Mul(velocity))
	}
	if (direction == BACKWARD){
		localCamera.position = localCamera.position.Sub(localCamera.front.Mul(velocity))
	}
	if (direction == LEFT){
		localCamera.position = localCamera.position.Sub(localCamera.right.Mul(velocity))
	}
	if (direction == RIGHT){
		localCamera.position = localCamera.position.Add(localCamera.right.Mul(velocity))
	}
}
//获取view
func (localCamera *LocalCamera) GetViewMatrix() *float32{
	target := localCamera.position.Add(localCamera.front)
	view := mgl32.LookAtV(localCamera.position,target, localCamera.up)
	return &view[0]
}
//更新view
func (localCamera *LocalCamera) updateCameraVectors(){
	x := math.Cos(mgl64.DegToRad(localCamera.yaw)) * math.Cos(mgl64.DegToRad(localCamera.pitch))
	y := math.Sin(mgl64.DegToRad(localCamera.pitch))
	z := math.Sin(mgl64.DegToRad(localCamera.yaw)) * math.Cos(mgl64.DegToRad(localCamera.pitch));
	localCamera.front = mgl32.Vec3{float32(x),float32(y),float32(z)}

	localCamera.right = localCamera.front.Cross(localCamera.wordUp).Normalize()
	localCamera.up = localCamera.right.Cross(localCamera.front).Normalize()
}

3. Create Shader

The last article I wrote a shader to create all the processes, where we will be packaged as a shader class can be constructed directly from the file and compile a shader

package shader

import (
	"io/ioutil"
	"fmt"
	"github.com/go-gl/gl/v4.1-core/gl"
	"strings"
)

type LocalShader struct{
	ID uint32
}

func (shader *LocalShader) Use(){
	gl.UseProgram(shader.ID)
}

func (shader *LocalShader) SetBool(name string, value bool){
	var a int32 = 0;
	if(value){
		a = 1
	}
	gl.Uniform1i(gl.GetUniformLocation(shader.ID, gl.Str(name + "\x00")), a)
}

func (shader *LocalShader) SetInt(name string, value int32){
	gl.Uniform1i(gl.GetUniformLocation(shader.ID, gl.Str(name + "\x00")), value)
}

func (shader *LocalShader) SetFloat(name string, value float32){
	gl.Uniform1f(gl.GetUniformLocation(shader.ID, gl.Str(name + "\x00")), value)
}

func (shader *LocalShader) SetMatrix4fv(name string, value *float32){
	gl.UniformMatrix4fv(gl.GetUniformLocation(shader.ID, gl.Str(name + "\x00")), 1,false,value)
}

func NewLocalShader(vertexPath string, fragmentPath string) *LocalShader{
	vertexString, err := ioutil.ReadFile(vertexPath)
	if err != nil{
        panic(err)
	}
	fragmentString, err := ioutil.ReadFile(fragmentPath)
	if err != nil{
        panic(err)
	}

	return NewStringShader(string(vertexString),string(fragmentString))
}

func NewStringShader(vertexString string, fragmentString string) *LocalShader{
	vertexShader,err := compileShader(vertexString+"\x00", gl.VERTEX_SHADER)
	if err != nil{
        panic(err)
	}
	fragmentShader,err := compileShader(fragmentString+"\x00", gl.FRAGMENT_SHADER)
	if err != nil{
        panic(err)
	}

	progID := gl.CreateProgram()
	gl.AttachShader(progID, vertexShader)
    gl.AttachShader(progID, fragmentShader)    
	gl.LinkProgram(progID)
	gl.DeleteShader(vertexShader)
	gl.DeleteShader(fragmentShader)
	return &LocalShader{ ID: progID}
}

func compileShader(source string, shaderType uint32) (uint32, error) {
    shader := gl.CreateShader(shaderType)
    csources, free := gl.Strs(source)
    gl.ShaderSource(shader, 1, csources, nil)
    free()
	gl.CompileShader(shader)
	
    var status int32
    gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
    if status == gl.FALSE {
        var logLength int32
        gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)
        log := strings.Repeat("\x00", int(logLength+1))
        gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))
        return 0, fmt.Errorf("failed to compile %v: %v", source, log)
    }
    return shader, nil
}

Two shader code as follows

#version 410 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main(){
    gl_Position = projection * view * model * vec4(aPos,1.0);
    TexCoord = aTexCoord;
}
#version 410 core
out vec4 FragColor;

in vec2 TexCoord;
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.5);
}

4. Integration

We maincarried out the integration of the above method, the process comprising key input, mouse movement processing

package main

import(
    "github.com/go-gl/glfw/v3.2/glfw"
    "github.com/go-gl/gl/v4.1-core/gl"
    "log"
    "legend/shader"
    "runtime"
    "legend/texture"
    "legend/camera"
    "github.com/go-gl/mathgl/mgl32"
)
const (
    width  = 800
    height = 600
)
var (
    vertices = []float32 {
       -0.5, -0.5, -0.5,  0.0, 0.0,
        0.5, -0.5, -0.5,  1.0, 0.0,
        0.5,  0.5, -0.5,  1.0, 1.0,
        0.5,  0.5, -0.5,  1.0, 1.0,
       -0.5,  0.5, -0.5,  0.0, 1.0,
       -0.5, -0.5, -0.5,  0.0, 0.0,
   
       -0.5, -0.5,  0.5,  0.0, 0.0,
        0.5, -0.5,  0.5,  1.0, 0.0,
        0.5,  0.5,  0.5,  1.0, 1.0,
        0.5,  0.5,  0.5,  1.0, 1.0,
       -0.5,  0.5,  0.5,  0.0, 1.0,
       -0.5, -0.5,  0.5,  0.0, 0.0,
   
       -0.5,  0.5,  0.5,  1.0, 0.0,
       -0.5,  0.5, -0.5,  1.0, 1.0,
       -0.5, -0.5, -0.5,  0.0, 1.0,
       -0.5, -0.5, -0.5,  0.0, 1.0,
       -0.5, -0.5,  0.5,  0.0, 0.0,
       -0.5,  0.5,  0.5,  1.0, 0.0,
   
        0.5,  0.5,  0.5,  1.0, 0.0,
        0.5,  0.5, -0.5,  1.0, 1.0,
        0.5, -0.5, -0.5,  0.0, 1.0,
        0.5, -0.5, -0.5,  0.0, 1.0,
        0.5, -0.5,  0.5,  0.0, 0.0,
        0.5,  0.5,  0.5,  1.0, 0.0,
   
       -0.5, -0.5, -0.5,  0.0, 1.0,
        0.5, -0.5, -0.5,  1.0, 1.0,
        0.5, -0.5,  0.5,  1.0, 0.0,
        0.5, -0.5,  0.5,  1.0, 0.0,
       -0.5, -0.5,  0.5,  0.0, 0.0,
       -0.5, -0.5, -0.5,  0.0, 1.0,
   
       -0.5,  0.5, -0.5,  0.0, 1.0,
        0.5,  0.5, -0.5,  1.0, 1.0,
        0.5,  0.5,  0.5,  1.0, 0.0,
        0.5,  0.5,  0.5,  1.0, 0.0,
       -0.5,  0.5,  0.5,  0.0, 0.0,
       -0.5,  0.5, -0.5,  0.0, 1.0,
    };
    position = []mgl32.Mat3{
        mgl32.Mat3{0,0,0},
        mgl32.Mat3{2,5,-15}, 
        mgl32.Mat3{-1.5,-2.2,-2.5}, 
    }
    deltaTime = float32(0.0);	// time between current frame and last frame
    lastFrame = float32(0.0);
    acamera = camera.NewDefaultCamera()
    firstMouse = true
    lastX = width / 2.0
    lastY = height / 2.0
)
func main() {
    runtime.LockOSThread()
    window := initGlfw()
    defer glfw.Terminate()
    initOpenGL()
    vao,vbo := makeVao(vertices,nil)

    shader := shader.NewLocalShader("./shader/shader-file/shader.vs","./shader/shader-file/shader.fs")
    shader.Use()
    shader.SetInt("texture1", 0)
    shader.SetInt("texture2", 1)

    texture1 := texture.NewLocalTexture("./texture/texture-file/face.jpg",gl.TEXTURE0)
    texture2 := texture.NewLocalTexture("./texture/texture-file/wood.jpg",gl.TEXTURE1)
    texture1.Use()
    texture2.Use()

    projection := acamera.GetProjection(width,height)
    shader.SetMatrix4fv("projection", projection)
    for !window.ShouldClose() {
        currentFrame := float32(glfw.GetTime());
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        clear()
        texture1.Use()
        texture2.Use()
        view := acamera.GetViewMatrix()
        shader.SetMatrix4fv("view",view)
        for _, v := range position {
            model := mgl32.HomogRotate3DX(float32(glfw.GetTime())).Mul4(mgl32.HomogRotate3DY(float32(glfw.GetTime())))
            model = mgl32.Translate3D(v[0],v[1],v[2]).Mul4(model)
            shader.SetMatrix4fv("model",&model[0])
            draw(vao)
        }
        processInput(window)
        glfw.PollEvents()
        window.SwapBuffers()
    }
    gl.DeleteVertexArrays(1, &vao);
    gl.DeleteBuffers(1, &vbo);
    glfw.Terminate()
}
func initGlfw() *glfw.Window {
    if err := glfw.Init(); err != nil {
            panic(err)
    }
    glfw.WindowHint(glfw.Resizable, glfw.False)
    window, err := glfw.CreateWindow(width, height, "test", nil, nil)
    window.SetCursorPosCallback(mouse_callback)
    if err != nil {
            panic(err)
    }
    window.MakeContextCurrent()
    window.SetInputMode(glfw.CursorMode,glfw.CursorDisabled)
    return window
}
func initOpenGL(){
    if err := gl.Init(); err != nil {
            panic(err)
    }
    version := gl.GoStr(gl.GetString(gl.VERSION))
    log.Println("OpenGL version", version)
    gl.Enable(gl.DEPTH_TEST)
}

func makeVao(points []float32,indices []uint32) (uint32,uint32) {
    var vbo uint32
    gl.GenBuffers(1, &vbo)
    gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
    gl.BufferData(gl.ARRAY_BUFFER,4*len(points), gl.Ptr(points), gl.STATIC_DRAW)

    var vao uint32
    gl.GenVertexArrays(1, &vao)
    gl.BindVertexArray(vao)
    
    gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 5 * 4, gl.PtrOffset(0))
    gl.EnableVertexAttribArray(0)
    gl.VertexAttribPointer(1, 2, gl.FLOAT, false, 5 * 4, gl.PtrOffset(3 * 4))
    gl.EnableVertexAttribArray(1)

    if(indices != nil){
        var ebo uint32
        gl.GenBuffers(2,&ebo)
        gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER,ebo)
        gl.BufferData(gl.ELEMENT_ARRAY_BUFFER,4*len(indices),gl.Ptr(indices),gl.STATIC_DRAW)

    }
    return vao,vbo
}

func processInput(window *glfw.Window){
    if(window.GetKey(glfw.KeyW) == glfw.Press){
        acamera.ProcessKeyboard(camera.FORWARD,deltaTime)
    }
    if(window.GetKey(glfw.KeyS) == glfw.Press){
        acamera.ProcessKeyboard(camera.BACKWARD,deltaTime)
    }
    if(window.GetKey(glfw.KeyA) == glfw.Press){
        acamera.ProcessKeyboard(camera.LEFT,deltaTime)
    }
    if(window.GetKey(glfw.KeyD) == glfw.Press){
        acamera.ProcessKeyboard(camera.RIGHT,deltaTime)
    }
    if(window.GetKey(glfw.KeyEscape) == glfw.Press){
        window.SetShouldClose(true)
    } 
}

func mouse_callback(window *glfw.Window, xpos float64, ypos float64){
    if(firstMouse){
        lastX = xpos
        lastY = ypos
        firstMouse = false
    }
    xoffset := float32(xpos - lastX)
    yoffset := float32(lastY - ypos) 

    lastX = xpos
    lastY = ypos

    acamera.ProcessMouseMovement(xoffset, yoffset)
}

func draw(vao uint32) {
    gl.BindVertexArray(vao)
    gl.DrawArrays(gl.TRIANGLES,0,36)
}
func clear(){
    gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 
}

5. Results

Here Insert Picture Description

Guess you like

Origin blog.csdn.net/qq_35488769/article/details/94431818