golang、OpenGL、コンピュータ グラフィックス (2)

コードリポジトリ

https://github.com/phprao/go-graphic

変身

行列演算とベクトル演算: https://learnopengl-cn.github.io/01%20Getting%20started/07%20Transformations/

OpenGL では、通常、何らかの理由で4x4変換行列を使用します。最も重要な理由は、ほとんどのベクトルに 4 つの成分があることです。

行列は実際には配列です

type Mat4 [16]float32

func Ident4() Mat4 {
    
    
	return Mat4{
    
    1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}
}
ベクトルのスケーリング

ここに画像の説明を挿入します

ベクトル変位

ここに画像の説明を挿入します

同次座標

ベクトルの w コンポーネントは とも呼ばれます齐次坐标同種のベクトルから 3D ベクトルを取得するには、x、y、z 座標を w 座標で除算します。w コンポーネントは通常 1.0 であるため、通常はこの問題に気づきません。同次座標の使用にはいくつかの利点があります。3D ベクトルを移動できるようになります (w コンポーネントなしではベクトルを移動できません)。

ベクトルの同次座標が 0 の場合、この座標は方向ベクトルです。w 座標が 0 であるため、このベクトルは移動できません (注: これは、一方向に移動できないと言うことです)。

ディスプレイスメント マトリックスを使用すると、オブジェクトを x、y、z の 3 方向に移動できます。これは、変換ツールボックスの非常に便利な変換マトリックスです。

ベクトルの回転

ここに画像の説明を挿入します

任意の軸に沿って回転

ここに画像の説明を挿入します

マトリックスを結合するときは、最初にスケーリング操作を実行し、次に回転操作を実行し、最後に変位操作を実行することをお勧めします。そうしないと、マトリックスが相互に影響を及ぼします。

GLM は Open GL Mathematicalsの略語です。バージョン 0.9.9 以降、GLM ライブラリはデフォルトで行列タイプを恒等行列 (対角要素が 1、その他の要素が 1) ではなく、ゼロ行列 (すべての要素が 0) に初期化します。は0です)。

golangのパッケージに対応github.com/go-gl/mathgl/mgl32

// 生成一个向量
v4 = mgl32.Vec4{
    
    1, 1, 1, 1}
v3 = mgl32.Vec3{
    
    3, 3, 3}
v4 = v3.Vec4(1)
v2 = v3.Vec2()

// 向量的Add, Sub, Mul, Dot, Cross, Len

// 矩阵之间点乘
A.Mul4(B)

// 生成4*4的单位矩阵
model := mgl32.Ident4()

// 生成一个沿向量(3,4,5)移动的变换矩阵trans3d
/*
[1, 0, 0, 3]
[0, 1, 0, 4]
[0, 0, 1, 5]
[0, 0, 0, 1]
*/
trans3d := mgl32.Translate3D(3,4,5)
// 使向量vec3(1,2,3)沿着向量(3,4,5)移动
// 结果 (4,6,8)
mgl32.TransformCoordinate(mgl32.Vec3{
    
    1, 2, 3}, trans3d)

// 生成缩放比例(2,2,2)的变换矩阵
// 如果缩放的比例是负值,会导致图像翻转
/*
[2, 0, 0, 0]
[0, 2, 0, 0]
[0, 0, 2, 0]
[0, 0, 0, 1]
*/
scale3d := mgl32.Scale3D(2, 2, 2)
// 使向量vec3(1,2,3)缩放(2,2,2)
// 结果 (2,4,6)
mgl32.TransformCoordinate(mgl32.Vec3{
    
    1, 2, 3}, scale3d)

// 沿轴(3,3,3)旋转20度的变化矩阵
/*
[5.735343 2.588426 8.066097 0.000000]
[8.066097 5.735343 2.588426 0.000000]
[2.588426 8.066097 5.735343 0.000000]
[0.000000 0.000000 0.000000 1.000000]
*/
rotate3d := mgl32.HomogRotate3D(mgl32.DegToRad(20), mgl32.Vec3{
    
    3, 3, 3})
// 使向量vec3(1,2,3)沿轴(3,3,3)旋转20度
// 结果 (35.11049, 27.302063, 35.92665)
mgl32.TransformCoordinate(mgl32.Vec3{
    
    1, 2, 3}, rotate3d)

最下層は度ではなくmath.Sin(angle)ラジアンを受け入れるため、ラジアン値と変換関数もここで渡す必要がありますradiandegreemgl32.RadToDeg() 和 mgl32.DegToRad()

変換行列のタイプはmgl32.Mat4type です。これをシェーダーに渡すためにuniformを使用します。

model := mgl32.Ident4()
modelUniform := gl.GetUniformLocation(program, gl.Str("model\x00"))
gl.UniformMatrix4fv(modelUniform, 1, false, &model[0])
uniform mat4 model;

例: 既存のテクスチャの場合、最初にスケーリング、次に回転、そして移動の効果を実現します。

ここに画像の説明を挿入します

施術後の効果

ここに画像の説明を挿入します

メインコード:

for !window.ShouldClose() {
    
    
    ......
    gl.UseProgram(program)

    rotate := mgl32.HomogRotate3D(mgl32.DegToRad(90), mgl32.Vec3{
    
    0, 0, 1})
    scale := mgl32.Scale3D(0.5, 0.5, 0.5)
    translate := mgl32.Translate3D(0.5, -0.5, 0)
    // 顺序要反着看:依次是 scale,rotate,translate
    transe := translate.Mul4(rotate).Mul4(scale)
    gl.UniformMatrix4fv(gl.GetUniformLocation(program, gl.Str("transe\x00")), 1, false, &transe[0])
    ......
}

頂点シェーダ

......
uniform mat4 transe;
......
void main() {
    
    
    gl_Position = transe * vec4(vPosition, 1.0);
    ......
}

回転円弧を時間の経過とともに変化させて、画像を回転させることができます

rotate := mgl32.HomogRotate3D(float32(glfw.GetTime()), mgl32.Vec3{
    
    0, 0, 1})

glfw.GetTime()返される時間はウィンドウが作成された時刻から測定され、単位は秒で、値は次のとおりです。

0.19938110100045076
0.3682488961570842
0.8834820281800326
...
1.0471016692995818
1.2141550765655154
1.380787958668221
...

ウィンドウが実行されている秒数を示します。

例 2: ウィンドウ内に 2 つのボックスを描画します。1 つは常に回転し、もう 1 つは常に縮小および拡大します。

for !window.ShouldClose() {
    
    
    gl.ClearColor(0.2, 0.3, 0.3, 1.0)
    gl.Clear(gl.COLOR_BUFFER_BIT)
    gl.UseProgram(program)

    gl.ActiveTexture(gl.TEXTURE0)
    gl.BindTexture(gl.TEXTURE_2D, texture1)
    gl.Uniform1i(gl.GetUniformLocation(program, gl.Str("ourTexture1"+"\x00")), 0)

    gl.ActiveTexture(gl.TEXTURE1)
    gl.BindTexture(gl.TEXTURE_2D, texture2)
    gl.Uniform1i(gl.GetUniformLocation(program, gl.Str("ourTexture2"+"\x00")), 1)

    gl.BindVertexArray(vao)

    // 第一个箱子
    rotate := mgl32.HomogRotate3D(float32(glfw.GetTime()), mgl32.Vec3{
    
    0, 0, 1}) // 旋转效果
    scale := mgl32.Scale3D(0.5, 0.5, 0.5)
    translate := mgl32.Translate3D(0.5, -0.5, 0)
    transe := translate.Mul4(rotate).Mul4(scale)
    gl.UniformMatrix4fv(gl.GetUniformLocation(program, gl.Str("transe\x00")), 1, false, &transe[0])
    gl.DrawElements(gl.TRIANGLES, pointNum, gl.UNSIGNED_INT, gl.Ptr(indices))

    // 第二个箱子
    rotate2 := mgl32.HomogRotate3D(mgl32.DegToRad(90), mgl32.Vec3{
    
    0, 0, 1})
    s := float32(math.Sin(glfw.GetTime()))
    scale2 := mgl32.Scale3D(s, s, s)
    translate2 := mgl32.Translate3D(-0.5, 0.5, 0)
    transe2 := translate2.Mul4(rotate2).Mul4(scale2)
    gl.UniformMatrix4fv(gl.GetUniformLocation(program, gl.Str("transe\x00")), 1, false, &transe2[0])
    gl.DrawElements(gl.TRIANGLES, pointNum, gl.UNSIGNED_INT, gl.Ptr(indices))

    glfw.PollEvents()
    window.SwapBuffers()
}

ここに画像の説明を挿入します


座標系

頂点が最終的​​にフラグメントに変換される前に通過する必要があるすべてのさまざまな状態。

  • ローカル空間 (オブジェクト空間とも呼ばれます)
  • ワールドスペース
  • 観察空間 (ビュー スペース)、視覚空間 (アイ スペース)、カメラ スペース (カメラ スペース) とも呼ばれます。
  • クリップスペース
  • スクリーンスペース

ある座標系から別の座標系に座標を変換するには、いくつかの変換行列を使用する必要がありますが、最も重要なものは、モデル、ビュー、投影の 3 つの行列です。頂点座標はローカル空間 (ローカル空間) から始まり、ローカル座標 (ローカル座標) と呼ばれ、後に世界座標 (ワールド座標)、観測座標 (ビュー座標)、クリッピング座標 (クリップ座標)、最後に画面座標(スクリーン座標)の形で終了します。以下の図は、プロセス全体と各変換プロセスの内容を示しています。

ここに画像の説明を挿入します

行列変換
  • ローカル空間からワールド空間への変換: モデル マトリックス。
  • ワールド空間から観察空間への変換: マトリックスの表示。
  • 観察空間からクリッピング空間への変換: 射影行列。

すべての頂点がクリッピング空間に変換されると、透视除法(Perspective Division)位置ベクトルの x、y、z 成分をベクトルの同次 w 成分で除算する最終操作が実行されます。遠近法による除算は、4D クリッピングを分割することです。 space into 3D の標準化されたデバイス座標への座標変換のプロセス。このステップは、頂点シェーダーの実行の最後に実行されます自动执行

観測座標をクリッピング座標に変換する射影行列は 2 つの異なる形式にすることができ、それぞれが異なる錐台を定義します。正射投影矩阵(Orthographic Projection Matrix)どちらかを作成することを選択できます透视投影矩阵(Perspective Projection Matrix)

正投影法

幅、高さ、ニアプレーン、ファープレーンで指定します。

// near平面为靠近观察者的平面
// 参数一二,表示near平面的左右坐标
// 参数三四,表示near平面的底顶坐标
// 参数五六,near和far平面距离屏幕的距离
mgl32.Ortho(0, 800, 0, 600, 0.1, 100)

ここに画像の説明を挿入します

正投影では、近くのオブジェクトと遠くのオブジェクトの両方を同等に扱います。つまり、各頂点の w コンポーネントは 1 ですが、これは現実と矛盾します。実際、同じサイズのオブジェクトは人間の目から遠くに見えることになります。つまり、これは目の構造によって決まります。

透視投影
// 第一个参数为视野角,通常为45度。
// 第二个参数为视口的宽高比。
// near和far平面距离屏幕的距离。通常设置near为0.1,far为100
mgl32.Perspective(mgl32.DegToRad(45.0), float32(windowWidth)/windowHeight, 0.1, 10.0)

ここに画像の説明を挿入します

カメラから離れるほど見える範囲は広くなりますが、画面サイズは固定されているため、オブジェクトは小さくなります。

w コンポーネントが変更されます。頂点座標が観測点から離れるほど、w コンポーネントは大きくなります。最終的に、x、y、z は w 成分で除算されるため、遠くにあるオブジェクトは小さくなります。

視野角の特性: 視野角が小さいほど、見える範囲が狭くなり、スクリーンへの投影は拡大する効果があり、逆に視野角が大きいほど、縮小する効果があります。

最終的な変換プロセス:

V_clip = M_projection * M_view * M_model * V_local
具体的な実践

モデルマトリックス

部屋(ワールド空間)に2つのテーブルを描きました。ワールド座標系の原点の左右にあります。左側のテーブルに入って描画すると、座標原点はその中心に配置されます。テーブルは描画の便宜のために作成されています。この時点ではローカル空間です。ペイント後、全体的な効果を確認するにはワールド空間に戻る必要があります。最初にテーブルを小さくする必要があります (大部分を占めるようにするため)小さくなると、シーン全体が見えますが、先ほど描いたテーブルの中心がワールド空間の中心にあるので、移動する必要があります。テーブルを斜めに配置する必要がある場合は、 、最初に回転する必要があります。也就是说 Model 是用来调整单个的物体、移動、拡大縮小、回転の 3 つの操作を連続的に実行する必要があることがわかります。そうであるはずです缩放 --> 旋转 --> 平移

マトリックスを表示する

ワールド空間に到着したら、その空間をどの角度から観察すればよいのか、つまりグラフィックツールとしてどの角度からユーザーにシーンを提示すればよいのか、ここにカメラの概念があり、次のように理解できます。ユーザーの目でカメラを後方に動かすことは、シーン全体を前方に動かすことと同じです也就是说 View 是用来调整整个场景的OpenGL は右手座標系であるため、正の Z 軸に沿って移動する必要があります。これを行うには、シーンを負の Z 軸に沿って移動します。それは私たちに後退しているような感覚を与えます。3 次元のシーンがそこに静止しており、人々はそれをさまざまな点から観察することを選択できると想像してください。カメラは回転できず、常に画面に対して垂直に見られます。もちろん、カメラを回転させることもできます。これもまた知識のポイントです。

射影行列

シーンをウィンドウ領域に投影します。これにより、シーンのどの部分がスクリーンに投影されるかが決まります。

model := mgl32.HomogRotate3D(mgl32.DegToRad(-55), mgl32.Vec3{
    
    1, 0, 0})
view := mgl32.Translate3D(0, 0, -3)
projection := mgl32.Perspective(mgl32.DegToRad(45), float32(width)/float32(height), 0.1, 100)
transe := projection.Mul4(view).Mul4(model)
gl.UniformMatrix4fv(gl.GetUniformLocation(program, gl.Str("transe\x00")), 1, false, &transe[0])

3D 効果がある場合は、オクルージョンを処理する深度テストをオンにする必要があります。そうしないと、効果がおかしくなります。

gl.Enable(gl.DEPTH_TEST)

gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

カメラ

上記の座標系はカメラが動いているのではなく、シーンが動いていて物体が動いていると仮定していますが、実際にはカメラも動かすという観察方法もあります。

カメラを定義するとは、互いに直交する 3 つの単位軸を持ち、カメラの位置を原点とする座標系を作成することです。

ここに画像の説明を挿入します

カメラを定義する手順:

  • カメラの位置: ワールド空間内の点。カメラがここに配置されていることを意味します。位置が遠くなるほど、見えるオブジェクトは小さくなりますP1(x,y,z)

    cameraPos := mgl32.Vec3{
          
          0, 0, 3}
    
  • カメラの方向: ワールド空間内で、カメラが指す場所は 1 つの方向のみを表すため、その方向の点を選択するだけです。たとえば、カメラが原点を指す場合、カメラの方向は次のようになります。P2(0,0,0)写真の青い矢印ですP1-P2が、この方向はカメラが写真を撮る方向とは逆です。

    cameraTarget := mgl32.Vec3{
          
          0, 0, 0}
    cameraDirction := cameraPos.Sub(cameraTarget)
    
  • 右軸: X 軸の正方形とも呼ばれます。正しいベクトルを取得するには、少しトリックを使用する必要があります。最初にアップ ベクトル (アップ ベクトル) を定義します。次に、上ベクトルと 2 番目のステップで得られた方向ベクトルを相互乗算します。2 つのベクトルの外積の結果は、同時に両方のベクトルに垂直になるため、(2 つのベクトルの外積の順序を入れ替えると、x 軸の正の方向を指すベクトルが得られます) 、x 軸の負の方向を指す反対のベクトルが得られます)。

    up := mgl32.Vec3{
          
          0, 1, 0}
    cameraRight := up.Cross(cameraDirction)
    
  • 上軸: x 軸ベクトルと z 軸ベクトルが得られたので、カメラの方向を指す正の y 軸ベクトルを取得するのは比較的簡単です。右ベクトルと方向ベクトルの外積を求めます。

    cameraUp := cameraDirction.Cross(cameraRight)
    

そこで、3 つのベクトルが与えられている限りcameraPos, cameraTarget, up、カメラ座標を構築できることがわかり、LookAt関数が存在します。

camera := mgl32.LookAtV(cameraPos, cameraTarget, up)

使用中、実際にはカメラがビューとなるため、順序は ですprojection * camera * model

例1

立方体は Y 軸を中心に回転します。通常は Y 軸しか見えませんが、カメラを追加すると 3 次元効果を見ることができます。

model := mgl32.HomogRotate3D(float32(glfw.GetTime()), mgl32.Vec3{
    
    0, 1, 0})
camera := mgl32.LookAtV(mgl32.Vec3{
    
    2, 2, 2}, mgl32.Vec3{
    
    0, 0, 0}, mgl32.Vec3{
    
    0, 1, 0})
projection := mgl32.Perspective(mgl32.DegToRad(45), float32(width)/height, 0.1, 100)
transe := projection.Mul4(camera).Mul4(model)
gl.UniformMatrix4fv(gl.GetUniformLocation(program, gl.Str("transe\x00")), 1, false, &transe[0])

ここに画像の説明を挿入します

例 2

シーンは静止しており、カメラの位置は半径 3 の円の周りを回転します。

radius := 3.0
cx := float32(math.Sin(glfw.GetTime()) * radius)
cz := float32(math.Cos(glfw.GetTime()) * radius)
camera := mgl32.LookAtV(mgl32.Vec3{
    
    cx, 2, cz}, mgl32.Vec3{
    
    0, 0, 0}, mgl32.Vec3{
    
    0, 1, 0})
projection := mgl32.Perspective(mgl32.DegToRad(45), float32(width)/height, 0.1, 100)
transe := projection.Mul4(camera)
gl.UniformMatrix4fv(gl.GetUniformLocation(program, gl.Str("transe\x00")), 1, false, &transe[0])

ここに画像の説明を挿入します

例 3

WSAD ボタンを使用してカメラを左右に動かし、

cameraPos := mgl32.Vec3{
    
    0, 0, 3}
cameraFront := mgl32.Vec3{
    
    0, 0, -1}
cameraUp := mgl32.Vec3{
    
    0, 1, 0}

func KeyPressAction(window *glfw.Window) {
    
    
	keyCallback := func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
    
    
		cameraSpeed := float32(0.05)
		if key == glfw.KeyW && action == glfw.Press {
    
    
			cameraPos = cameraPos.Sub(cameraFront.Mul(cameraSpeed))
		}
		if key == glfw.KeyS && action == glfw.Press {
    
    
			cameraPos = cameraPos.Add(cameraFront.Mul(cameraSpeed))
		}
		if key == glfw.KeyA && action == glfw.Press {
    
    
             // Normalize 标准化坐标使其落在 [-1,1]
			cameraPos = cameraPos.Add(cameraFront.Cross(cameraUp).Normalize().Mul(cameraSpeed))
		}
		if key == glfw.KeyD && action == glfw.Press {
    
    
			cameraPos = cameraPos.Sub(cameraFront.Cross(cameraUp).Normalize().Mul(cameraSpeed))
		}
         // log.Println(cameraPos, cameraPos.Add(cameraFront))
	}
	window.SetKeyCallback(keyCallback)
}

func Run10() {
    
    
	......
	KeyPressAction(window)

	for !window.ShouldClose() {
    
    
		......
         // 这样能保证无论我们怎么移动,摄像机都会注视着目标方向
		camera := mgl32.LookAtV(cameraPos, cameraPos.Add(cameraFront), cameraUp)
		projection := mgl32.Perspective(mgl32.DegToRad(45), float32(width)/height, 0.1, 100)
		transe := projection.Mul4(camera)
		gl.UniformMatrix4fv(gl.GetUniformLocation(program, gl.Str("transe\x00")), 1, false, &transe[0])

		......

		glfw.PollEvents()
		window.SwapBuffers()
	}
}

A と D は直交する必要があるため、ベクトルが使用されます叉乘

上記のコメントの一部をどのように理解すればよいでしょうか?詳しく見るために印刷を追加しましょう。WSAD 操作は这样能保证无论我们怎么移动,摄像机都会注视着目标方向のみを変更し、Z 方向のみを変更するため、これによりカメラの向きが常に Z 軸と平行になります。最初と同じです。keyCallbackcameraPoscameraFrontcameraTarget = cameraPos + cameraFront


視点の変更
オイラー角

オイラー角は、3D 空間内の任意の回転を表現できる 3 つの値で、18 世紀にレオンハルト オイラーによって提案されました。オイラー角にはピッチ角 (Pitch)、ヨー角 (Yaw)、ロール角 (Roll) の 3 種類があり、それぞれの意味を次の図に示します。

ここに画像の説明を挿入します

ピッチ角は、上または下をどのように見ているかを表す角度であり、最初の画像で確認できます。2 番目の画像はヨー角を示しており、左右のどれだけを見ているかを表します。ロール角度はカメラの回転方法を表し、宇宙船のカメラで一般的に使用されます。各オイラー角には値があり、3 つの角度を組み合わせることで 3D 空間内の任意の回転ベクトルを計算できます。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

最終的な方向ベクトルの計算式

// direction代表摄像机的前轴(Front),这个前轴是和本文第一幅图片的第二个摄像机的方向向量是相反的
direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); 
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
マウスコントロール

マウス座標系の原点は画面の左上隅で、正の X は右、正の Y は下となるため、Y 軸の増分を逆にする必要があります。

初めて入ったとき、ジッターが発生します。これは、デフォルトのカーソル X とカーソル Y が画面の中心にあり、最初はマウスが画面の中心にないため、開始点を初期化する必要があります。

ピッチ角は、ユーザーが 89 度を超えて見えないようにする必要があり (視野角は 90 度で反転します)、-89 度未満にすることもできません。これにより、ユーザーは空または足元のみを見ることができますが、この制限を超えることはできません。人間の目が上と下を見るのと同じように、90 度を超えると見えるものが反転します。

ヨー角は 360 度回転できます。

yaw合計値の初期値を0にするとpitch、マウスを動かすとすぐに空白になってしまいますが、これはカメラの向きに問題があり、カメラの向きが決まってしまっているためです。では、どのように初期値を設定すればよいのでしょうかcameraFront?cameraFrontの初期値は(0,0,-1)、マウスが画面に入った後、急激な変化ではなく直線的に変化するはずです。したがって、yawと のpitch初期値はcameraFront = (0,0,-1)、その式を分析することでわかりますpitch = 0, yaw = -90

var firstMouse bool
var cursorX float64 = 400
var cursorY float64 = 300
var yaw float64 = -90
var pitch float64
sensitivity := 0.05 // 鼠标移动的灵敏度
cursorPosCallback := func(w *glfw.Window, xpos float64, ypos float64) {
    
    
    if firstMouse {
    
    
        cursorX = xpos
        cursorY = ypos
        firstMouse = false
    }

    xoffset := sensitivity * (xpos - cursorX)
    yoffset := sensitivity * (cursorY - ypos)
    cursorX = xpos
    cursorY = ypos
    yaw += xoffset
    pitch += yoffset
    if pitch > 89 {
    
    
        pitch = 89
    }
    if pitch < -89 {
    
    
        pitch = -89
    }

    cameraFront = mgl32.Vec3{
    
    
        float32(math.Cos(float64(mgl32.DegToRad(float32(pitch)))) * math.Cos(float64(mgl32.DegToRad(float32(yaw))))),
        float32(math.Sin(float64(mgl32.DegToRad(float32(pitch))))),
        float32(math.Cos(float64(mgl32.DegToRad(float32(pitch)))) * math.Sin(float64(mgl32.DegToRad(float32(yaw))))),
    }.Normalize()
}
window.SetCursorPosCallback(cursorPosCallback)

スカイボックス
についてもお話しましたが、デフォルトではテクスチャマップが両面に貼り付けられているため、立方体の内側も貼り付けられていますが、よく見てみると外側と内側の絵がズレていることがわかります。このときカメラを向けると立方体の中心に位置を置くとどうなるでしょうか?実はこれがスカイボックスの効果ですマウスを操作することで、内部空間。もちろん、より詳細なスカイボックスには、6 つの側面に異なるテクスチャが必要になります。

もちろん、スカイ ボックスの正しい操作はキューブ マップを使用すること、つまりテクスチャ座標は 3 次元である必要があります。実装は https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/ を参照してください。 06%20キューブマップ

まずは立方体のテクスチャを作成します

func MakeTextureCube(filepathArray []string) uint32 {
    
    
	var texture uint32
	gl.GenTextures(1, &texture)
	gl.BindTexture(gl.TEXTURE_CUBE_MAP, texture)

	for i := 0; i < len(filepathArray); i++ {
    
    
		imgFile2, _ := os.Open(filepathArray[i])
		defer imgFile2.Close()
		img2, _, _ := image.Decode(imgFile2)
		rgba2 := image.NewRGBA(img2.Bounds())
		draw.Draw(rgba2, rgba2.Bounds(), img2, image.Point{
    
    0, 0}, draw.Src)

		// right, left, top, bottom, back, front
		//
		// TEXTURE_CUBE_MAP_POSITIVE_X   = 0x8515
		// TEXTURE_CUBE_MAP_NEGATIVE_X   = 0x8516
		// TEXTURE_CUBE_MAP_POSITIVE_Y   = 0x8517
		// TEXTURE_CUBE_MAP_NEGATIVE_Y   = 0x8518
		// TEXTURE_CUBE_MAP_POSITIVE_Z   = 0x8519
		// TEXTURE_CUBE_MAP_NEGATIVE_Z   = 0x851A
		gl.TexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X+uint32(i), 0, gl.RGBA, int32(rgba2.Rect.Size().X), int32(rgba2.Rect.Size().Y), 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(rgba2.Pix))
	}

	gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
	gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
	gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE)
	gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR)

	return texture
}

次に頂点データですが、テクスチャ座標を設定する必要はなく、頂点座標を直接テクスチャ座標として取得します。

#version 410

in vec3 vPosition;
out vec3 textureDir;

uniform mat4 transe;

void main() {
    
    
	gl_Position = transe * vec4(vPosition, 1.0);
	textureDir = vPosition;
}

フラグメントシェーダで使用されるsamplerCube

#version 410

in vec3 textureDir;

out vec4 frag_colour;

uniform samplerCube cubemap;

void main() {
    
    
	frag_colour = texture(cubemap, textureDir);
}

写真を渡すときも順番通りですright, left, top, bottom, back, front

スクロールホイールでズームを制御

視野 (Field of View) またはfov は、私たちが見ることができるシーンの範囲を定義します。視野が狭くなると、シーンが投影する空間が狭くなり、ズームインしたように感じられます(Zoom In)。マウスホイールを使用してズームインします。マウスの移動やキーボード入力と同様に、マウス ホイールのコールバック関数が必要です。マウス ホイールを回転させるとき、yoff 値は垂直スクロールのサイズを表します。scrollCallback関数が呼び出されると、グローバル変数fov変数の内容が変更されます。45.0fこれはデフォルトの視野値であるため、ズーム レベル1.0fを に制限します45.0f

var fov float64 = 45
scrollCallback := func(w *glfw.Window, xoff float64, yoff float64) {
    
    
    if fov >= 1.0 && fov <= 45.0 {
    
    
        fov -= yoff
    }
    if fov <= 1.0 {
    
    
        fov = 1.0
    }
    if fov >= 45.0 {
    
    
        fov = 45.0
    }
}
window.SetScrollCallback(scrollCallback)
......
projection := mgl32.Perspective(mgl32.DegToRad(float32(fov)), float32(width)/height, 0.1, 100)

画像を保存する

ctrl+sキーボードイベントを設定して一度保存するなど、現在のウィンドウのグラフィックを画像として保存できます。glfw がダブル バッファを使用することはわかっていますが、この関数はフロント バッファのデータを読み取ります
gl.ReadPixels()
func ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer)

x と y は開始点の座標を表し、ウィンドウの左下隅は (0,0)、上方向の Y は正、右方向の X は正です。次に、インターセプトされる幅と高さ、および最後の 3 つのパラメーターがあります。は関数と同じですgl.TexImage2D()

ただし、使用するグラフィックス ライブラリは基本的に左上隅を (0,0) 点として使用するため、保存された画像の Y 軸が上下逆になっているため、自分で Y 軸を反転する必要があります。

func (c *Camera) SavePng(filepath string) {
    
    
	img := image.NewRGBA(image.Rect(0, 0, c.WindowWidth, c.WindowHeight))

	gl.ReadPixels(0, 0, int32(c.WindowWidth), int32(c.WindowHeight), gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(img.Pix))

	// 翻转Y坐标
	for x := 0; x < c.WindowWidth; x++ {
    
    
		for y := 0; y < c.WindowHeight/2; y++ {
    
    
			s := img.RGBAAt(x, y)
			t := img.RGBAAt(x, c.WindowHeight-1-y)
			img.SetRGBA(x, y, t)
			img.SetRGBA(x, c.WindowHeight-1-y, s)
		}
	}

	if filepath == "" {
    
    
		filepath = strconv.Itoa(int(time.Now().Unix())) + ".png"
	}
	f, _ := os.Create(filepath)
	b := bufio.NewWriter(f)
	png.Encode(b, img)
	b.Flush()
	f.Close()
}

おすすめ

転載: blog.csdn.net/raoxiaoya/article/details/131429420