网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
-
- camera.go
一.参考资料
learnOpenGl 的中文翻译,使用C++
实现的。
go-gl example go-gl的示例代码
二.基础概念
这里涉及到的概念在之前的文章里基本上都有过介绍,不再赘述。不过大家有兴趣可以去看一看碰撞检测的一些算法实现
三.依赖
没有新增任何依赖
四.资源准备
我们创建的游戏世界里有两个地方需要用到纹理资源(贴图),一是组成世界的方块、二是游戏主角。由于方块是静态的,不需要动画效果,所以只需要一张贴图就可以了。而游戏主角则需要多张纹理图像来组成运动时的动画。要注意的是,为了只渲染一张图片中我们需要的部分,纹理图片必须是带有alpha
通道的图片格式,并将图片中不需要渲染的位置的透明度设置为0。为了简便,这里统一使用png
格式。
作者是直接百度搜索像素图像资源
找了一张gif
然后用screentogif
分解成静态图片,最后用开源图形编辑软件Krita
直接扣出来的(肯定是不能用于商业用途)由于没有人物静态图,我自己画了一个,资源列表如下
1.人物静止图
2.人物运动图(只展示第一帧)
2.方块纹理图
将资源准备完成之后,就能开始代码的开发了
五.开始实现!
1.资源管理
在上一篇文章中我们将纹理和着色器分别封装成了两个类,这里我们创建一个资源管理类对这两个类进行管理,由于golang
中是没有静态变量的,需要用包内变量对其进行模拟
shader.go
package resource
import(
“github.com/go-gl/gl/v4.1-core/gl”
“github.com/go-gl/mathgl/mgl32”
“strings”
“fmt”
)
type Shader struct{
ID uint32
}
func Compile(vertexString, fragmentString string) *Shader{
vertexShader,err := compile(vertexString+“\x00”, gl.VERTEX_SHADER)
if err != nil{
panic(err)
}
fragmentShader,err := compile(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 &Shader{ ID: progID}
}
func (shader *Shader) Use(){
gl.UseProgram(shader.ID)
}
func (shader *Shader) 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 *Shader) SetInt(name string, value int32){
gl.Uniform1i(gl.GetUniformLocation(shader.ID, gl.Str(name + “\x00”)), value)
}
func (shader *Shader) SetFloat(name string, value float32){
gl.Uniform1f(gl.GetUniformLocation(shader.ID, gl.Str(name + “\x00”)), value)
}
func (shader *Shader) SetMatrix4fv(name string, value *float32){
gl.UniformMatrix4fv(gl.GetUniformLocation(shader.ID, gl.Str(name + “\x00”)), 1,false,value)
}
func (shader *Shader) SetVector3f(name string, vec3 mgl32.Vec3){
gl.Uniform3f(gl.GetUniformLocation(shader.ID, gl.Str(name + “\x00”)), vec3[0], vec3[1], vec3[2]);
}
func compile(sourceString string, shaderType uint32)(uint32, error){
shader := gl.CreateShader(shaderType)
source, free := gl.Strs(sourceString)
gl.ShaderSource(shader, 1, source, 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
}
shader
类从给定的两个字符串中编译出顶点着色器和片段着色器,同时提供几个用于设置uniform
变量的函数,唯一要说明的是字符串尾部要添加0X00
来标识结尾(发现只有golang
需要这样)
两个着色器程序
着色器程序的用途大家可以在参考资料或者之前的文章中找到详细说明,这里只从代码上大概讲解一下。需要说明的是,着色器程序需要结合具体要绘制的顶点来看,在这个2D游戏中,所有的元素都是由两个三角形组成的矩形构成的,因此在不使用EBO
的情况下需要六个顶点
顶点
vertices := []float32{
0.0, 1.0, 0.0, 1.0,
1.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 1.0,
1.0, 1.0, 1.0, 1.0,
1.0, 0.0, 1.0, 0.0,
}
由于我们开发的是2D游戏,所有顶点的z分量都为0,所以用顶点中每一行的前两位作为顶点坐标,后两位作为纹理坐标,并将顶点坐标和纹理坐标合为一个变量传入着色器中
顶点着色器
#version 410 core
layout (location = 0) in vec4 vertex;
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 projection;
uniform mat4 view;
uniform int reverseX;
void main()
{ if(reverseX == 1){
TexCoords = vertex.zw;
}else{
TexCoords = vec2(1 - vertex.z, vertex.w);
}
gl_Position = projection * view * model * vec4(vertex.x, vertex.y, 0.0, 1.0);
}
着色器程序中model
、projection
,view
这三个变量用于将物体从局部空间变换到世界空间并转换为用户视角,可以理解为负责对物体进行平移,变形,对画面进行裁剪的工具。在前面的文章中已经讲过,不再赘述。vertex
变量就是前面顶点数据中的每一行,其中前两位代表顶点坐标,后两位代表纹理坐标,reverseX
变量为unifom
类型,同样从外部传入,负责控制图像是否沿Y轴方向进行镜像,具体作用后面会讲到
片段着色器
#version 410 core
in vec2 TexCoords;
out vec4 FragColor;
uniform sampler2D image;
uniform vec3 spriteColor;
void main()
{
vec4 texColor = texture(image, TexCoords);
if(texColor.a < 0.1)
discard;
FragColor = vec4(spriteColor, 1.0) * texColor;
}
片段着色器与上一篇文章的基本相同,唯一区别是加入了一个判断,在图像区域的透明度小于0.1的时候,会放弃对这片区域的渲染。
texture2D.go
package resource
import(
“os”
“image”
“image/png”
“image/draw”
“errors”
“github.com/go-gl/gl/v4.1-core/gl”
)
type Texture2D struct{
ID uint32
TEXTUREINDEX uint32
}
func NewTexture2D(file string, TEXTUREINDEX uint32) *Texture2D{
imgFile, err := os.Open(file)
if err != nil {
panic(err)
}
img, err := png.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.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))
gl.BindTexture(gl.TEXTURE_2D, 0);
return &Texture2D{ ID: textureID,TEXTUREINDEX:TEXTUREINDEX}
}
func (texture *Texture2D) Use(){
gl.ActiveTexture(texture.TEXTUREINDEX)
gl.BindTexture(gl.TEXTURE_2D, texture.ID)
}
texture2D
类用于解析png格式的图片并创建纹理绑定到上下文中
resource.go
package resource
import (
“io/ioutil”
)
var (
textures = make(map[string]*Texture2D)
shaders = make(map[string]*Shader)
)
func LoadShader(vShaderFile, fShaderFile, name string){
vertexString, err := ioutil.ReadFile(vShaderFile)
if err != nil{
panic(err)
}
fragmentString, err := ioutil.ReadFile(fShaderFile)
if err != nil{
panic(err)
}
shaders[name] = Compile(string(vertexString), string(fragmentString))
}
func GetShader(name string) *Shader{
return shaders[name]
}
func LoadTexture(TEXTUREINDEX uint32, file, name string){
texture := NewTexture2D(file, TEXTUREINDEX)
textures[name] = texture
}
func GetTexture(name string) *Texture2D{
return textures[name]
}
负责加载纹理和着色器的管理类
2.游戏对象
首先想一想游戏内所有元素都有的属性有哪些,并对其进行封装,创建一个游戏对象基类
gameObj.go
package model
import(
“game2D/resource”
“game2D/sprite”
“github.com/go-gl/mathgl/mgl32”
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
e string) *Texture2D{
return textures[name]
}
负责加载纹理和着色器的管理类
2.游戏对象
首先想一想游戏内所有元素都有的属性有哪些,并对其进行封装,创建一个游戏对象基类
gameObj.go
package model
import(
“game2D/resource”
“game2D/sprite”
“github.com/go-gl/mathgl/mgl32”
[外链图片转存中…(img-0hAjQqXA-1715714986759)]
[外链图片转存中…(img-zbghEq10-1715714986759)]
[外链图片转存中…(img-9Nd64Go9-1715714986759)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新