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”
)
type GameObj struct{
texture *resource.Texture2D
x float32
y float32
size *mgl32.Vec2
rotate float32
color *mgl32.Vec3
isXReverse int32
}
func(gameObj GameObj) GetPosition()mgl32.Vec2{
return mgl32.Vec2{gameObj.x, gameObj.y}
}
func(gameObj *GameObj) SetPosition(position mgl32.Vec2){
gameObj.x = position[0]
gameObj.y = position[1]
}
func(gameObj GameObj) GetSize()mgl32.Vec2{
return mgl32.Vec2{gameObj.size[0], gameObj.size[1]}
}
func(gameObj *GameObj) Draw(renderer *sprite.SpriteRenderer){
renderer.DrawSprite(gameObj.texture, &mgl32.Vec2{gameObj.x,gameObj.y}, gameObj.size, gameObj.rotate, gameObj.color,gameObj.isXReverse)
}
func(gameObj *GameObj) ReverseX(){
gameObj.isXReverse = -1
}
func(gameObj *GameObj) ForWardX(){
gameObj.isXReverse = 1
}
func NewGameObj(texture *resource.Texture2D, x, y float32, size *mgl32.Vec2, rotate float32, color *mgl32.Vec3) *GameObj{
return &GameObj{texture:texture,
x:x,
y:y,
size:size,
rotate:rotate,
color:color,
isXReverse:1}
}
一个游戏内的对象一定会有那些属性?位置,大小,外观(纹理),颜色,和旋转的角度。同时为了效率,加入一个标识是否镜像的变量。有了这些属性,我们就能直接将其绘制到屏幕上。
3.精灵
我们已经有了可供绘制游戏对象,接下来自然就是将他绘制到屏幕上了,创建一个精灵绘制类
SpriteRenderer .go
package sprite
import(
“github.com/go-gl/mathgl/mgl32”
“game2D/resource”
“github.com/go-gl/gl/v4.1-core/gl”
)
type SpriteRenderer struct{
shader *resource.Shader
vao uint32
}
func NewSpriteRenderer(shader *resource.Shader) *SpriteRenderer{
spriteRenderer := SpriteRenderer{shader:shader}
spriteRenderer.initRenderData()
return &spriteRenderer
}
func(spriteRenderer *SpriteRenderer) DrawSprite(texture *resource.Texture2D, position *mgl32.Vec2, size *mgl32.Vec2, rotate float32, color *mgl32.Vec3,isReverseX int32){
model := mgl32.Translate3D(position[0], position[1], 0).Mul4(mgl32.Translate3D(0.5size[0], 0.5size[1], 0))
model = model.Mul4(mgl32.HomogRotate3D(rotate, mgl32.Vec3{0, 0, 1}))
model = model.Mul4(mgl32.Translate3D(-0.5size[0], -0.5size[1], 0))
model = model.Mul4(mgl32.Scale3D(size[0], size[1], 1))
spriteRenderer.shader.SetMatrix4fv(“model”, &model[0])
spriteRenderer.shader.SetInt(“reverseX”, isReverseX)
spriteRenderer.shader.SetVector3f(“spriteColor”, *color)
texture.Use()
gl.BindVertexArray(spriteRenderer.vao);
gl.DrawArrays(gl.TRIANGLES, 0, 6);
gl.BindVertexArray(0);
}
func(spriteRenderer *SpriteRenderer) initRenderData(){
var vbo uint32
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,
}
gl.GenVertexArrays(1, &spriteRenderer.vao);
gl.GenBuffers(1, &vbo);
gl.BindBuffer(gl.ARRAY_BUFFER, vbo);
gl.BufferData(gl.ARRAY_BUFFER, 4 * len(vertices), gl.Ptr(vertices), gl.STATIC_DRAW);
gl.BindVertexArray(spriteRenderer.vao);
gl.EnableVertexAttribArray(0);
gl.VertexAttribPointer(0, 4, gl.FLOAT, false, 4 * 4, gl.PtrOffset(0));
gl.BindBuffer(gl.ARRAY_BUFFER, 0);
gl.BindVertexArray(0);
}
这个类中作用initRenderData
方法基本上和上一篇文章里的makeVao
函数一样,创建顶点缓冲对象和顶点数组对象并将其绑定到上下文,而在DrawSprite
中我们根据物体大小和位置对其进行缩放平移,并绑定物体纹理,将其绘制到屏幕上