此文写在golang游戏开发学习笔记-用golang画一个随时间变化颜色的正方形之后,感兴趣可以先去那篇文章了解一些基础知识,在这篇文章里,我们将创建一个非常简单(只有三个方块)但能自由探索的的3D世界

1.参考资料

learnOpenGl 的中文翻译,使用C++实现的。
go-gl example go-gl的示例代码

2.基础概念

  1. 相关数学概念如矩阵向量等,有兴趣的可以在网上找相关资料
  2. 纹理,可以理解为我们创建的模型的贴图
  3. 纹理坐标,取值范围在(0,0)到(1,1)之间,对应于纹理图像中的左下角和右上角
  4. 纹理环绕方式,纹理坐标超出范围之后做什么处理,比如重复纹理图像,填充别的颜色等等
  5. 纹理过滤,纹理的像素并不与坐标一一对应,需要我们指定映射方式
  6. 局部空间,可以理解为创建物体时会选定一个基准点,所有该物体的其他顶点都时相对于基准点而排布,这些顶点的坐标就叫局部坐标,构成的空间被称为局部空间
  7. 世界空间,这个比较好理解,可以理解为整个游戏的场景,创建物体之后需经过矩阵运算将物体移动到世界空间中
  8. 观察空间,顾名思义,从观察者的角度所看到的世界,同样是通过矩阵运算将世界空间坐标转化为观察者所看到的坐标
  9. 裁剪空间,受限于窗口大小,我们不可能看到整个世界空间,需要对世界空间进行裁剪,保留窗口所能显示的部分

所以我们需要三个矩阵,第一个负责将局部坐标(local)转化为世界坐标(负责物体移动)命名为model,第二个负责将世界坐标转化为我们从观察者角度所看到的坐标,命名为view,第三个对我们所能看到的世界进行裁剪,显示在窗口上,命名为projection,即,一个顶点坐标要经历如下变换

clip = projection * view * model * local

以上概念可以在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 uint32TEXTUREINDEX 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 uint32gl.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.Vec3front       mgl32.Vec3up          mgl32.Vec3right     mgl32.Vec3wordUp      mgl32.Vec3yaw   float64pitch float64zoom  float32movementSpeed float32mouseSensitivity float32constrainPitch 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 := truelocalCamera := &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.mouseSensitivityyoffset *= localCamera.mouseSensitivitylocalCamera.yaw += float64(xoffset)localCamera.pitch += float64(yoffset)// Make sure that when pitch is out of bounds, screen doesn't get flippedif (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 shaderimport ("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 int32gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)if status == gl.FALSE {var logLength int32gl.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);
}

4.整合

我们在main方法中对以上内容进行整合,包括按键输入处理,鼠标移动处理等

package mainimport("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  = 800height = 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 framelastFrame = float32(0.0);acamera = camera.NewDefaultCamera()firstMouse = truelastX = width / 2.0lastY = 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 uint32gl.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 uint32gl.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 uint32gl.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 = xposlastY = yposfirstMouse = false}xoffset := float32(xpos - lastX)yoffset := float32(lastY - ypos) lastX = xposlastY = yposacamera.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.结果

golang游戏开发学习笔记-创建一个能自由探索的3D世界相关推荐

  1. golang游戏开发学习笔记-开发一个简单的2D游戏(基础篇)

    此文写在golang游戏开发学习笔记-创建一个能自由探索的3D世界之后,感兴趣可以先去那篇文章了解一些基础知识,在这篇文章里我们要创建一个简单的2D游戏场景以及配套的人物,并实现人物运动和碰撞检测功能 ...

  2. 【Unity3D游戏开发学习笔记】(六)上帝之手—GameObject的操作

    在Unity中,所有实体都属于游戏对象(GameObject),比如外部导入到场景中的模型,Unity自带的立方体等等,而要将这些GameOject进行管理,交互等操作,则需要用到脚本来实现,上一节我 ...

  3. 小狐狸横版游戏开发学习笔记(上)

    小狐狸横版游戏开发学习笔记(上) 目录 小狐狸横版游戏开发学习笔记(上) 1.关于如何创建Tilemap 2.关于地图格子之间出现间隙的问题 3.如何设置自己想要的控制按键 4.如何解决玩家移动过程中 ...

  4. 游戏开发学习笔记——lua脚本语言——安装、汉化与小测试(解决lua运行代码乱码问题)

    游戏开发学习笔记--lua脚本语言--安装.汉化与小测试 FOR THE SIGMA FOR THE GTINDER FOR THE ROBOMASTER 简介: Lua 是一种轻量小巧的脚本语言,用 ...

  5. 【麦可网】Cocos2d-X跨平台游戏开发学习笔记---第二十一课:Cocos2D-X网格特效1-3

    [麦可网]Cocos2d-X跨平台游戏开发---学习笔记 第二十一课:Cocos2D-X网格特效1-3 ================================================ ...

  6. 嵌入式开发学习笔记9-做一个好玩的LED闪烁

    嵌入式开发学习笔记9-做一个好玩的LED闪烁 前言 实际操作 程序功能 实现思路 程序源码 实现效果展示 前言 LED小灯闪烁实质是控制单片机上的I/O口,通过向I/O口循环输入高低电平,从而控制LE ...

  7. 桌面破坏王游戏开发学习笔记总结

    桌面破坏王开发学习笔记总结 目录 桌面破坏王开发学习笔记总结 1.GetSystemMetrics() 2.MoveWindow() 3.TextOutW() 4.C++11->Function ...

  8. 【Unity3D游戏开发学习笔记】(一)Unity3D初认识

    一.什么是Unity3D Unity是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏.建筑可视化.实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合 ...

  9. Coursera 游戏开发学习笔记(week 2)

    游戏类型的划分 1.RPG游戏 在游戏中,玩家需要创建或者扮演一个虚拟的游戏角色.游戏包括完整的故事情节,并以推进的方式将剧情进行深度的演绎.故事情节. 游戏战斗. 角色升级. 装备收集.都是角色扮演 ...

最新文章

  1. Bootstrap3.1开发的响应式个人简历模板
  2. 利用边缘灰度变化建模,来提高圆环直径求取精度
  3. idea设置包为层级结构?
  4. 类模板非类型形参实现Stack
  5. Oracle游标有何用,Oracle游标使用方法有哪些?
  6. TensorFlow学习笔记(十九) 基本算术运算和Reduction归约计算
  7. when will BOL flag lv_do_reread be set
  8. 证监会:对贾跃亭、杨丽杰采取终身证券市场禁入措施
  9. php 类 和 函数,PHP函数和类
  10. 凭据分配没有加密oracle_远程连接身份验证错误,又找不到加密Oracle修正
  11. 动态规划问题以及诸多实例分析
  12. 2017.4.18-morning
  13. 装机软件搜狗拼音输入法v11.5.0.5352去广告精简优化版
  14. windows对计算机硬件有要求吗,win10对硬件有什么要求_win10硬件配置有哪些要求
  15. PS知识点大总结(一)——基础操作
  16. 【人类简史】从动物到上帝 [以色列-尤瓦尔 · 赫拉利](阅读笔记)
  17. 雪崩效应 (密码学术语)
  18. 渗透工具SharpXDecrypt:Xshell全版本凭证一键恢复工具,针对Xshell全版本在本地保存的密码进行解密
  19. Android蓝牙音乐获取歌曲信息
  20. 实战 | flink sql 实时 TopN

热门文章

  1. Hdoj 3486 Interviewer(二分加RMQ)
  2. 相似图片搜索原理一(ahash—c++实现)
  3. Hive学习—数据操作
  4. 微博桌面2015登录时显示“网络异常,请重新登录”,网络环境非常好,qq跟网页都正常开,为什么呢...
  5. 支付宝扫福字原理及快速获得“福卡”方法
  6. 天地图加载403错误
  7. 网摘:WINDOWS所有系统文件的用途
  8. 好几天忘记笑了~2012年9月10日
  9. c实现多播客户端与服务端
  10. Found conflicts! Looking for incompatible packages.