2024年Go最新golang游戏开发学习笔记-创建一个能自由探索的3D世界,2024年最新Golang实战项目视频

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

以上概念可以在learnOpenGl 中找到详细的概念解释,这里只做个概要

3.依赖

C++opengl有配套的矩阵运算包GLM(OpenGL Mathematics),然而作者并没能找到基于golangGLM包,只找到了一个名为mgl的包,仔细查看了一下源代码,需要的矩阵运算几乎都有,要注意的是,这个依赖包依赖于image模块,而官方的image模块被qiang了,所以最好是在gopath目录里手动创建golang.org\x目录,然后在github镜像里直接下载依赖保存到目录中,在安装好image依赖后运行

go get github.com/go-gl/mathgl/

4.实现

1.创建用于加载纹理的纹理类

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)

}

在构造函数中我们需要传入一个jpg格式的纹理文件路径(调用image/图片格式包下的Decode方法可以解析不同格式文件,这里为了简单,只解析jpg格式),和一个uint32类型的参数,这个参数指定在我们使用多个纹理时,着色器程序中不同纹理的占位符。在构造函数中我们指定了纹理放大缩小过滤方式和纹理环绕过滤方式

2.创建用于变换的矩阵

前面提到过我们要用到三个矩阵来对物体坐标进行变换

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

这个矩阵会将物体沿着z轴方向移动一个单位

接着调用mglPerspective方法创建一个透视矩阵,设置视野大小为45,近平面设为0.1,远平面设为100,只有在这两个平面之间的物体才会被渲染,可以理解为物体远离到了看不见的位置

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

最后我们使用mglLookAt方法创建一个视图矩阵,它用于将物体坐标转化为我们观察者的视角坐标,这个方法需要三个向量,第一个是观察者处于世界空间中的哪个位置,第二个是观察者要观察的方向,最后是表示观察者所观察方向的正上方的向量(可以通过右向量和观察方向向量叉乘得到)

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)

代码中position代表观察者所处的位置(设为原点),front代表观察者观察的方向(沿z轴的负方向),up代表竖直方向。

至此,我们准备好了创建一个3D空间所需要的全部矩阵,之后的所有操作都转化为了对这三个矩阵的运算,要移动物体可以通过修改model矩阵实现,视角需要变化只需要修改view矩阵,视野缩放责可以通过修改projection矩阵来完成。

有个问题是,如何让我们观察的方向随着鼠标移动而变化?前面知道,front代表观察者观察的方向,修改这个向量即可,这里要用到欧拉角,具体可以去网上找资料,我也不是很懂,这里直接复制了教程中的代码

最后为了进一步抽象相关操作,创建一个camera类

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.创建着色器

上一篇文章中我写过创建一个着色器的全部流程,这里我们将其封装为一个着色器类,可以直接从文件中构造并编译出着色器

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

}

两个着色器代码如下

#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);

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

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);

[外链图片转存中…(img-NBOS3pTd-1715380218815)]
[外链图片转存中…(img-KGIgYd1Z-1715380218815)]
[外链图片转存中…(img-heNpO42b-1715380218815)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值