From a242c7fcebf736d3edb0c75cf599b51ef36cf893 Mon Sep 17 00:00:00 2001
From: miku-666 <74728189+NessieHax@users.noreply.github.com>
Date: Wed, 31 Jan 2024 20:02:07 +0100
Subject: [PATCH] SkinRenderer - Added simple skybox rendering
---
PCK-Studio/PckStudio.csproj | 3 +
PCK-Studio/Properties/Resources.Designer.cs | 57 +++++++++-
PCK-Studio/Properties/Resources.resx | 9 ++
PCK-Studio/Rendering/SkinRenderer.cs | 97 ++++++++++++++++--
.../shader/skyboxFragmentShader.glsl | 12 +++
.../Resources/shader/skyboxVertexShader.glsl | 14 +++
PCK-Studio/Resources/skybox_texture.png | Bin 0 -> 1071157 bytes
7 files changed, 184 insertions(+), 8 deletions(-)
create mode 100644 PCK-Studio/Resources/shader/skyboxFragmentShader.glsl
create mode 100644 PCK-Studio/Resources/shader/skyboxVertexShader.glsl
create mode 100644 PCK-Studio/Resources/skybox_texture.png
diff --git a/PCK-Studio/PckStudio.csproj b/PCK-Studio/PckStudio.csproj
index cd7f3d5e..358ea7a0 100644
--- a/PCK-Studio/PckStudio.csproj
+++ b/PCK-Studio/PckStudio.csproj
@@ -641,6 +641,9 @@
+
+
+
diff --git a/PCK-Studio/Properties/Resources.Designer.cs b/PCK-Studio/Properties/Resources.Designer.cs
index a5516225..98a02888 100644
--- a/PCK-Studio/Properties/Resources.Designer.cs
+++ b/PCK-Studio/Properties/Resources.Designer.cs
@@ -222,6 +222,16 @@ namespace PckStudio.Properties {
}
}
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ public static System.Drawing.Bitmap DefaultSkyTexture {
+ get {
+ object obj = ResourceManager.GetObject("DefaultSkyTexture", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Bitmap.
///
@@ -590,6 +600,7 @@ namespace PckStudio.Properties {
///
///layout(location = 0) in vec4 vertexPosition;
///layout(location = 1) in vec2 texCoord;
+ ///layout(location = 2) in float scale;
///
///uniform mat4 u_ViewProjection;
///uniform mat4 u_Model;
@@ -599,7 +610,9 @@ namespace PckStudio.Properties {
///void main()
///{
/// v_TexCoord = texCoord;
- /// gl_Position = u_ViewProjection * u_Model * vertexPosition;
+ /// vec4 scaledVertex = scale * vertexPosition;
+ /// vec4 invertedVertex = vec4(scaledVertex.x, scaledVertex.y * -1.0, scaledVertex.z * -1.0, 1.0);
+ /// gl_Position = u_ViewProjection * u_Model * invertedVertex;
///};.
///
public static string skinVertexShader {
@@ -608,6 +621,48 @@ namespace PckStudio.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to #version 330 core
+ ///
+ ///layout(location = 0) out vec4 color;
+ ///
+ ///uniform samplerCube skybox;
+ ///
+ ///in vec3 texCoords;
+ ///
+ ///void main()
+ ///{
+ /// color = texture(skybox, texCoords);
+ ///}.
+ ///
+ public static string skyboxFragmentShader {
+ get {
+ return ResourceManager.GetString("skyboxFragmentShader", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to #version 330 core
+ ///
+ ///layout(location = 0) in vec4 a_Pos;
+ ///
+ ///uniform mat4 viewProjection;
+ ///
+ ///out vec3 texCoords;
+ ///
+ ///void main()
+ ///{
+ /// vec4 pos = viewProjection * a_Pos;
+ /// gl_Position = vec4(pos.x, pos.y, pos.ww);
+ /// texCoords = vec3(a_Pos.x, a_Pos.y, -a_Pos.z);
+ ///};.
+ ///
+ public static string skyboxVertexShader {
+ get {
+ return ResourceManager.GetString("skyboxVertexShader", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Bitmap.
///
diff --git a/PCK-Studio/Properties/Resources.resx b/PCK-Studio/Properties/Resources.resx
index 6d7bce1d..962702fc 100644
--- a/PCK-Studio/Properties/Resources.resx
+++ b/PCK-Studio/Properties/Resources.resx
@@ -334,4 +334,13 @@
..\Resources\shader\skinFragmentShader.glsl;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252
+
+ ..\Resources\skybox_texture.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\shader\skyboxFragmentShader.glsl;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252
+
+
+ ..\Resources\shader\skyboxVertexShader.glsl;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252
+
\ No newline at end of file
diff --git a/PCK-Studio/Rendering/SkinRenderer.cs b/PCK-Studio/Rendering/SkinRenderer.cs
index e29ed60b..9a9a2df9 100644
--- a/PCK-Studio/Rendering/SkinRenderer.cs
+++ b/PCK-Studio/Rendering/SkinRenderer.cs
@@ -31,6 +31,7 @@ using PckStudio.Forms.Editor;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Drawing.Imaging;
+using System.IO;
using PckStudio.Rendering.Camera;
using PckStudio.Rendering.Texture;
@@ -99,7 +100,7 @@ namespace PckStudio.Rendering
set
{
_globalModelRotation.X = MathHelper.Clamp(value.X, -90f, 90f);
- _globalModelRotation.Y = MathHelper.Clamp(value.Y, -180f, 180f);
+ _globalModelRotation.Y = value.Y % 360f;
}
}
@@ -114,8 +115,9 @@ namespace PckStudio.Rendering
{
set
{
- if (HasValidContext && _skinShader is not null)
+ if (value is not null && HasValidContext && _skinShader is not null)
{
+ MakeCurrent();
TextureSize = value.Width == value.Height ? new Size(64, 64) : new Size(64, 32);
UvTranslation = value.Width == value.Height ? new Vector2(1f / 64) : new Vector2(1f / 64, 1f / 32);
var texture = new Texture2D(value);
@@ -146,6 +148,12 @@ namespace PckStudio.Rendering
private SkinANIM _anim;
private Image _texture;
+ private Shader _skyboxShader;
+ private RenderBuffer _skyboxRenderBuffer;
+ private CubeTexture _skyboxTexture;
+ private float skyboxRotation = 0f;
+ private float skyboxRotationStep = 0.5f;
+
private Dictionary additionalModelRenderGroups;
private Dictionary partOffset;
private CubeRenderGroup head;
@@ -328,15 +336,76 @@ namespace PckStudio.Rendering
Trace.TraceInformation(GL.GetString(StringName.Version));
+ // Initialize skybox shader
+ {
+ string customSkyboxFilepath = Path.Combine(Program.AppData, "cubemap.png");
+ Image skyboxImage = File.Exists(customSkyboxFilepath)
+ ? Image.FromFile(customSkyboxFilepath)
+ : Resources.DefaultSkyTexture;
+
+ _skyboxShader = Shader.Create(Resources.skyboxVertexShader, Resources.skyboxFragmentShader);
+ _skyboxTexture = new CubeTexture(skyboxImage);
+ _skyboxTexture.Bind(1);
+ _skyboxShader.Bind();
+ _skyboxShader.SetUniform1("skybox", 1);
+ _skyboxShader.Validate();
+
+ Vector3[] cubeVertices = new Vector3[]
+ {
+ // front
+ new Vector3(-1.0f, -1.0f, 1.0f),
+ new Vector3( 1.0f, -1.0f, 1.0f),
+ new Vector3( 1.0f, 1.0f, 1.0f),
+ new Vector3(-1.0f, 1.0f, 1.0f),
+ // back
+ new Vector3(-1.0f, -1.0f, -1.0f),
+ new Vector3( 1.0f, -1.0f, -1.0f),
+ new Vector3( 1.0f, 1.0f, -1.0f),
+ new Vector3(-1.0f, 1.0f, -1.0f)
+ };
+
+ var skyboxVAO = new VertexArray();
+ var skyboxVBO = new VertexBuffer(cubeVertices, cubeVertices.Length * Vector3.SizeInBytes);
+ var vboLayout = new VertexBufferLayout();
+ vboLayout.Add(3);
+ skyboxVAO.AddBuffer(skyboxVBO, vboLayout);
+ var skybocIBO = IndexBuffer.Create(
+ // front
+ 0, 1, 2,
+ 2, 3, 0,
+ // right
+ 1, 5, 6,
+ 6, 2, 1,
+ // back
+ 7, 6, 5,
+ 5, 4, 7,
+ // left
+ 4, 0, 3,
+ 3, 7, 4,
+ // bottom
+ 4, 5, 1,
+ 1, 0, 4,
+ // top
+ 3, 2, 6,
+ 6, 7, 3);
+
+ _skyboxRenderBuffer = new RenderBuffer(skyboxVAO, skybocIBO, PrimitiveType.Triangles);
+ skyboxVAO.Unbind();
+ skybocIBO.Unbind();
+ }
+
+ // Initialize skin shader
+ {
_skinShader = Shader.Create(Resources.skinVertexShader, Resources.skinFragmentShader);
+ _skinShader.Validate();
_skinShader.Bind();
Texture ??= Resources.classic_template;
-
RenderTexture = Texture;
GLErrorCheck();
}
+ }
public void UpdateModelData()
{
@@ -472,9 +541,10 @@ namespace PckStudio.Rendering
MakeCurrent();
- camera.Update(ClientSize.Width / (float)ClientSize.Height);
+ camera.Update(AspectRatio);
var viewProjection = camera.GetViewProjection();
+ _skinShader.Bind();
_skinShader.SetUniformMat4("u_ViewProjection", ref viewProjection);
_skinShader.SetUniform2("u_TexScale", UvTranslation);
@@ -537,6 +607,20 @@ namespace PckStudio.Rendering
RenderBodyPart(new Vector3(0f, 12f, 0f), new Vector3(-2f, -12f, 0f), RightLegMatrix * legRightMatrix, modelMatrix, "LEG0", "PANTS0");
RenderBodyPart(new Vector3(0f, 12f, 0f), new Vector3( 2f, -12f, 0f), LeftLegMatrix * legLeftMatrix , modelMatrix, "LEG1", "PANTS1");
+ if (true)
+ {
+ GL.DepthFunc(DepthFunction.Lequal);
+ _skyboxShader.Bind();
+
+ var view = new Matrix4(new Matrix3(Matrix4.LookAt(camera.WorldPosition, camera.WorldPosition + camera.Orientation, camera.Up)))
+ * Matrix4.CreateRotationY(MathHelper.DegreesToRadians(skyboxRotation));
+ var proj = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(camera.Fov), AspectRatio, 1f, 1000f);
+ var viewproj = view * proj;
+ _skyboxShader.SetUniformMat4("viewProjection", ref viewproj);
+ Renderer.Draw(_skyboxShader, _skyboxRenderBuffer);
+ GL.DepthFunc(DepthFunction.Less);
+ }
+
SwapBuffers();
}
@@ -605,9 +689,8 @@ namespace PckStudio.Rendering
private void animationTimer_Tick(object sender, EventArgs e)
{
- if (!Focused)
- return;
-
+ skyboxRotation += skyboxRotationStep;
+ skyboxRotation %= 360f;
animationCurrentRotationAngle += animationRotationStep;
if (animationCurrentRotationAngle >= animationMaxAngleInDegrees || animationCurrentRotationAngle <= -animationMaxAngleInDegrees)
animationRotationStep = -animationRotationStep;
diff --git a/PCK-Studio/Resources/shader/skyboxFragmentShader.glsl b/PCK-Studio/Resources/shader/skyboxFragmentShader.glsl
new file mode 100644
index 00000000..b242f456
--- /dev/null
+++ b/PCK-Studio/Resources/shader/skyboxFragmentShader.glsl
@@ -0,0 +1,12 @@
+#version 330 core
+
+layout(location = 0) out vec4 color;
+
+uniform samplerCube skybox;
+
+in vec3 texCoords;
+
+void main()
+{
+ color = texture(skybox, texCoords);
+}
\ No newline at end of file
diff --git a/PCK-Studio/Resources/shader/skyboxVertexShader.glsl b/PCK-Studio/Resources/shader/skyboxVertexShader.glsl
new file mode 100644
index 00000000..7a955686
--- /dev/null
+++ b/PCK-Studio/Resources/shader/skyboxVertexShader.glsl
@@ -0,0 +1,14 @@
+#version 330 core
+
+layout(location = 0) in vec4 a_Pos;
+
+uniform mat4 viewProjection;
+
+out vec3 texCoords;
+
+void main()
+{
+ vec4 pos = viewProjection * a_Pos;
+ gl_Position = vec4(pos.x, pos.y, pos.ww);
+ texCoords = vec3(a_Pos.x, a_Pos.y, -a_Pos.z);
+};
\ No newline at end of file
diff --git a/PCK-Studio/Resources/skybox_texture.png b/PCK-Studio/Resources/skybox_texture.png
new file mode 100644
index 0000000000000000000000000000000000000000..d0f91faae5dff73fbf54dc63736d710dd32a082f
GIT binary patch
literal 1071157
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