尝试在 LWJGL 中使用着色器移动对象时我做错了什么?
What am I doing wrong when trying to move an object using shaders in LWJGL?
我正在尝试使用着色器将网格的点正确地转换和投影到 window 的表面(感兴趣的点是负责输出最终顶点位置的顶点着色器)。
模型(quad)由以下顶点表示:[-0.5f, 0.5f, 0f] (V0), [-0.5f, -0.5f, 0f] (V1), [0.5f, -0.5f, 0f] (V2), [0.5f, 0.5f, 0].
我有以下两种方法创建透视投影矩阵:
public static Matrix4f getProjectionMatrix(float fovy, int width, int height, float zNear, float zFar) {
float aspectRatio = (float) width / height;
projectionMatrix.perspective(fovy, aspectRatio, zNear, zFar);
return projectionMatrix;
}
此方法可在 Transformations class 创建并 returns 透视投影矩阵中找到。
fovy = 垂直视野
宽度 = window
的宽度
身高 = window
的身高
zNear = 近裁剪面
zFar = 远裁剪平面
public Matrix4f perspective(float fovy, float aspectRatio, float zNear, float zFar) {
float scale = (float) (Math.tan(fovy * 0.5) * zNear);
float top = scale;
float right = top * aspectRatio;
float bottom = -top;
float left = bottom * aspectRatio;
this.m00 = 2*zNear / (right - left);
this.m03 = (right + left) / (right - left);
this.m11 = 2*zNear / (top - bottom);
this.m12 = (top + bottom) / (top - bottom);
this.m22 = -(zFar + zNear) / (zFar - zNear);
this.m23 = -2*zFar*zNear / (zFar - zNear);
this.m32 = -1;
return this;
}
这个方法在Matrix4fclass.
这是 Matrix4f 的开头 class,带有 Matrix4f 实例变量和构造函数:
public class Matrix4f {
float m00, m01, m02, m03;
float m10, m11, m12, m13;
float m20, m21, m22, m23;
float m30, m31, m32, m33;
public Matrix4f() {
m00 = 1.0f; m01 = 0.0f; m02 = 0.0f; m03 = 0.0f;
m10 = 0.0f; m11 = 1.0f; m12 = 0.0f; m13 = 0.0f;
m20 = 0.0f; m21 = 0.0f; m22 = 1.0f; m23 = 0.0f;
m30 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = 1.0f;
}
调用构造函数后,单位矩阵已经创建。唯一剩下的就是知道如何初始化透视投影矩阵。这是在调用 class Transformations:
的构造函数时完成的
public class Transformations {
private Matrix4f translationMatrix;
private static Matrix4f projectionMatrix;
public Transformations() {
translationMatrix = new Matrix4f();
projectionMatrix = new Matrix4f();
}
现在是平移矩阵。 Transformations class 通过创建方法 getTranslationMatrix(float x, float y, float z)
提供了创建它的功能,并且 returns 使用参数的翻译矩阵:
public Matrix4f getTranslationMatrix(float x, float y, float z) {
translationMatrix.m03 = x;
translationMatrix.m13 = y;
translationMatrix.m23 = z;
return translationMatrix;
}
为了真正拥有代表游戏模型的东西,我创建了一个名为 GameEntity 的 class。它代表模型的网格和位置:
public class GameEntity {
private final Mesh mesh;
private Vector3f position;
public GameEntity(Mesh mesh) {
this.mesh = mesh;
position = new Vector3f(0, 0, 0);
}
public Vector3f getPosition() {
return position;
}
public void updatePosition(float x, float y, float z) {
position.x += x;
position.y += y;
position.z += z;
}
public Mesh getMesh() {
return mesh;
}
}
updatePosition
方法应该将实体作为一个整体围绕 window 移动。我不会在这里包含 Mesh class 的代码解释。您只需要知道它包含关于 GameEntity 的网格(顶点)数据,例如顶点位置、顶点颜色、索引等,它们都存储在一个顶点数组对象中,然后依次用于将 Mesh 实例渲染到 window 上。在我的例子中,形成四边形的两个三角形表示为 GameEntity 实例。
移动模型: updatePosition方法每次调用W,A,S,D,space 或左移被按下。它按设定的数量更新 GameEntity 实例(存储在 GameEntity[] entities
数组中索引 0 处)的位置:
private void processInput() {
glfwPollEvents();
if (window.keys[GLFW_KEY_W]) {
entities[0].updatePosition(0, 0, -1.0f);
} else if (window.keys[GLFW_KEY_S]){
entities[0].updatePosition(0, 0, 1.0f);
} else if (window.keys[GLFW_KEY_A]) {
entities[0].updatePosition(1.0f, 0, 0);
} else if (window.keys[GLFW_KEY_D]) {
entities[0].updatePosition(-1.0f, 0, 0);
} else if (window.keys[GLFW_KEY_SPACE]) {
entities[0].updatePosition(0, 1.0f, 0);
} else if (window.keys[GLFW_KEY_LEFT_SHIFT]) {
entities[0].updatePosition(0, -1.0f, 0);
}
}
此方法在主游戏循环中调用。
然后,在 Renderer class 中,模型的平移矩阵是基于它的位置和投影矩阵是根据window对象的属性构建的:
private ShaderProgram shaderProgram;
private Window window = new Window();
private final Transformations transformation;
private Matrix4f translationMatrix = new Matrix4f();
private Matrix4f projectionMatrix = new Matrix4f();
private static double angleOfView = 60.0;
private static final float FOVY = (float) Math.toRadians(angleOfView);
private static final float zNear = 0.01f;
private static final float zFar = 1000.0f;
shaderProgram.createUniform("translationMatrix");
shaderProgram.createUniform("projectionMatrix");
public void render(Window window, GameEntity[] entities) {
i++;
clear();
if (window.isResized()) {
glViewport(0, 0, window.getWidth(), window.getHeight());
window.setResized(false);
}
//make the shaders active
shaderProgram.bind();
//update the projection matrix
Matrix4f projectionMatrix = transformation.getProjectionMatrix(FOVY, window.getWidth(), window.getHeight(), zNear, zFar);
shaderProgram.setUniformMatrix("projectionMatrix", projectionMatrix);
//render each game item
for(GameEntity entity : entities) {
Matrix4f translationMat = transformation.getTranslationMatrix(entity.getPosition());
shaderProgram.setUniformMatrix("translationMatrix", translationMat);
entity.getMesh().render();
}
shaderProgram.unbind();
}
首先,定义所有统一位置(在 render()
方法之上)。
clear()
方法清除渲染缓冲区 - 它准备渲染新图像。在下面的 if 子句中,处理 window 调整大小操作。如果调整 window 的大小,则具有相应方法的 if 子句将更新 window 的 width
和 height
以匹配调整后的 window.
projectionMatrix
由定义为 Renderer class(FOVY
、zNear
、zFar
)的实例变量的变量构成,并且获取 window 对象(window.getWidth()
、window.getHeight()
)的当前宽度和高度的两个变量。
然后,通过调用shaderProgram.setUniformMatrix("projectionMatrix", projectionMatrix);
:
将投影矩阵“发送”到顶点着色器
private final Map<String, Integer> uniforms;
public void createUniform(String uniformName) throws Exception {
int uniformLocation = glGetUniformLocation(programID, uniformName);
if (uniformLocation < 0) {
throw new Exception("[ShaderProgram.createUniform]: Couldn't find uniform: " + uniformName);
}
uniforms.put(uniformName, uniformLocation);
}
此方法位于 ShaderProgram class 中,其中包含对活动着色器程序和统一变量的引用与之关联的存储在 uniforms
Hashmap..
然后,在 for 循环内,渲染两个四边形。首先,根据 GameInstance 的位置值构造平移矩阵,表示为三元组向量 (x, y, z)。然后,创建的矩阵被“发送”到顶点着色器。
现在,将透视投影 (projectionMatrix
) 和平移矩阵 (translationMatri
x) 发送到顶点着色器后,是时候调用 上的渲染方法了Mesh 实例来渲染它。 render()
方法的代码(在 entity.getMesh().render()
的上下文中)是:
public void render() {
glBindVertexArray(getVaoID());
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getIndicesVboID());
glDrawElements(GL_TRIANGLES, getVertexCount(), GL_UNSIGNED_INT, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(0);
glBindVertexArray(0);
}
然而,结果并不令人满意。当按下 W 或 S 时,四边形(两个三角形)离“我们”更远或更近,这是正确的。但是,当按下 A 或 D 时(应在 x 轴上平移模型),四边形(两个三角形)不会平移。 它只是围绕它的中心旋转。如何解决这个问题?
似乎 row/column 命名有问题。
据我了解,矩阵中的代码单元格 mij
是 col=i, row=j
OpenGL 需要一个 16 值数组,即 13、14、15 位置的转换。通常这称为 "column major order",第 4 列表示翻译。
所以试试这个:
public Matrix4f getTranslationMatrix(float x, float y, float z) {
translationMatrix.m30 = x;
translationMatrix.m31 = y;
translationMatrix.m32 = z;
return translationMatrix;
}
此外,请检查您的 perspective
。我认为您在 ij
索引中有一些错误。 ii
细胞看起来不错。
我正在尝试使用着色器将网格的点正确地转换和投影到 window 的表面(感兴趣的点是负责输出最终顶点位置的顶点着色器)。
模型(quad)由以下顶点表示:[-0.5f, 0.5f, 0f] (V0), [-0.5f, -0.5f, 0f] (V1), [0.5f, -0.5f, 0f] (V2), [0.5f, 0.5f, 0].
我有以下两种方法创建透视投影矩阵:
public static Matrix4f getProjectionMatrix(float fovy, int width, int height, float zNear, float zFar) {
float aspectRatio = (float) width / height;
projectionMatrix.perspective(fovy, aspectRatio, zNear, zFar);
return projectionMatrix;
}
此方法可在 Transformations class 创建并 returns 透视投影矩阵中找到。
fovy = 垂直视野
宽度 = window
的宽度身高 = window
的身高zNear = 近裁剪面
zFar = 远裁剪平面
public Matrix4f perspective(float fovy, float aspectRatio, float zNear, float zFar) { float scale = (float) (Math.tan(fovy * 0.5) * zNear); float top = scale; float right = top * aspectRatio; float bottom = -top; float left = bottom * aspectRatio; this.m00 = 2*zNear / (right - left); this.m03 = (right + left) / (right - left); this.m11 = 2*zNear / (top - bottom); this.m12 = (top + bottom) / (top - bottom); this.m22 = -(zFar + zNear) / (zFar - zNear); this.m23 = -2*zFar*zNear / (zFar - zNear); this.m32 = -1; return this;
} 这个方法在Matrix4fclass.
这是 Matrix4f 的开头 class,带有 Matrix4f 实例变量和构造函数:
public class Matrix4f {
float m00, m01, m02, m03;
float m10, m11, m12, m13;
float m20, m21, m22, m23;
float m30, m31, m32, m33;
public Matrix4f() {
m00 = 1.0f; m01 = 0.0f; m02 = 0.0f; m03 = 0.0f;
m10 = 0.0f; m11 = 1.0f; m12 = 0.0f; m13 = 0.0f;
m20 = 0.0f; m21 = 0.0f; m22 = 1.0f; m23 = 0.0f;
m30 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = 1.0f;
}
调用构造函数后,单位矩阵已经创建。唯一剩下的就是知道如何初始化透视投影矩阵。这是在调用 class Transformations:
的构造函数时完成的public class Transformations {
private Matrix4f translationMatrix;
private static Matrix4f projectionMatrix;
public Transformations() {
translationMatrix = new Matrix4f();
projectionMatrix = new Matrix4f();
}
现在是平移矩阵。 Transformations class 通过创建方法 getTranslationMatrix(float x, float y, float z)
提供了创建它的功能,并且 returns 使用参数的翻译矩阵:
public Matrix4f getTranslationMatrix(float x, float y, float z) {
translationMatrix.m03 = x;
translationMatrix.m13 = y;
translationMatrix.m23 = z;
return translationMatrix;
}
为了真正拥有代表游戏模型的东西,我创建了一个名为 GameEntity 的 class。它代表模型的网格和位置:
public class GameEntity {
private final Mesh mesh;
private Vector3f position;
public GameEntity(Mesh mesh) {
this.mesh = mesh;
position = new Vector3f(0, 0, 0);
}
public Vector3f getPosition() {
return position;
}
public void updatePosition(float x, float y, float z) {
position.x += x;
position.y += y;
position.z += z;
}
public Mesh getMesh() {
return mesh;
}
}
updatePosition
方法应该将实体作为一个整体围绕 window 移动。我不会在这里包含 Mesh class 的代码解释。您只需要知道它包含关于 GameEntity 的网格(顶点)数据,例如顶点位置、顶点颜色、索引等,它们都存储在一个顶点数组对象中,然后依次用于将 Mesh 实例渲染到 window 上。在我的例子中,形成四边形的两个三角形表示为 GameEntity 实例。
移动模型: updatePosition方法每次调用W,A,S,D,space 或左移被按下。它按设定的数量更新 GameEntity 实例(存储在 GameEntity[] entities
数组中索引 0 处)的位置:
private void processInput() {
glfwPollEvents();
if (window.keys[GLFW_KEY_W]) {
entities[0].updatePosition(0, 0, -1.0f);
} else if (window.keys[GLFW_KEY_S]){
entities[0].updatePosition(0, 0, 1.0f);
} else if (window.keys[GLFW_KEY_A]) {
entities[0].updatePosition(1.0f, 0, 0);
} else if (window.keys[GLFW_KEY_D]) {
entities[0].updatePosition(-1.0f, 0, 0);
} else if (window.keys[GLFW_KEY_SPACE]) {
entities[0].updatePosition(0, 1.0f, 0);
} else if (window.keys[GLFW_KEY_LEFT_SHIFT]) {
entities[0].updatePosition(0, -1.0f, 0);
}
}
此方法在主游戏循环中调用。
然后,在 Renderer class 中,模型的平移矩阵是基于它的位置和投影矩阵是根据window对象的属性构建的:
private ShaderProgram shaderProgram;
private Window window = new Window();
private final Transformations transformation;
private Matrix4f translationMatrix = new Matrix4f();
private Matrix4f projectionMatrix = new Matrix4f();
private static double angleOfView = 60.0;
private static final float FOVY = (float) Math.toRadians(angleOfView);
private static final float zNear = 0.01f;
private static final float zFar = 1000.0f;
shaderProgram.createUniform("translationMatrix");
shaderProgram.createUniform("projectionMatrix");
public void render(Window window, GameEntity[] entities) {
i++;
clear();
if (window.isResized()) {
glViewport(0, 0, window.getWidth(), window.getHeight());
window.setResized(false);
}
//make the shaders active
shaderProgram.bind();
//update the projection matrix
Matrix4f projectionMatrix = transformation.getProjectionMatrix(FOVY, window.getWidth(), window.getHeight(), zNear, zFar);
shaderProgram.setUniformMatrix("projectionMatrix", projectionMatrix);
//render each game item
for(GameEntity entity : entities) {
Matrix4f translationMat = transformation.getTranslationMatrix(entity.getPosition());
shaderProgram.setUniformMatrix("translationMatrix", translationMat);
entity.getMesh().render();
}
shaderProgram.unbind();
}
首先,定义所有统一位置(在 render()
方法之上)。
clear()
方法清除渲染缓冲区 - 它准备渲染新图像。在下面的 if 子句中,处理 window 调整大小操作。如果调整 window 的大小,则具有相应方法的 if 子句将更新 window 的 width
和 height
以匹配调整后的 window.
projectionMatrix
由定义为 Renderer class(FOVY
、zNear
、zFar
)的实例变量的变量构成,并且获取 window 对象(window.getWidth()
、window.getHeight()
)的当前宽度和高度的两个变量。
然后,通过调用shaderProgram.setUniformMatrix("projectionMatrix", projectionMatrix);
:
private final Map<String, Integer> uniforms;
public void createUniform(String uniformName) throws Exception {
int uniformLocation = glGetUniformLocation(programID, uniformName);
if (uniformLocation < 0) {
throw new Exception("[ShaderProgram.createUniform]: Couldn't find uniform: " + uniformName);
}
uniforms.put(uniformName, uniformLocation);
}
此方法位于 ShaderProgram class 中,其中包含对活动着色器程序和统一变量的引用与之关联的存储在 uniforms
Hashmap..
然后,在 for 循环内,渲染两个四边形。首先,根据 GameInstance 的位置值构造平移矩阵,表示为三元组向量 (x, y, z)。然后,创建的矩阵被“发送”到顶点着色器。
现在,将透视投影 (projectionMatrix
) 和平移矩阵 (translationMatri
x) 发送到顶点着色器后,是时候调用 上的渲染方法了Mesh 实例来渲染它。 render()
方法的代码(在 entity.getMesh().render()
的上下文中)是:
public void render() {
glBindVertexArray(getVaoID());
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getIndicesVboID());
glDrawElements(GL_TRIANGLES, getVertexCount(), GL_UNSIGNED_INT, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(0);
glBindVertexArray(0);
}
然而,结果并不令人满意。当按下 W 或 S 时,四边形(两个三角形)离“我们”更远或更近,这是正确的。但是,当按下 A 或 D 时(应在 x 轴上平移模型),四边形(两个三角形)不会平移。 它只是围绕它的中心旋转。如何解决这个问题?
似乎 row/column 命名有问题。
据我了解,矩阵中的代码单元格 mij
是 col=i, row=j
OpenGL 需要一个 16 值数组,即 13、14、15 位置的转换。通常这称为 "column major order",第 4 列表示翻译。
所以试试这个:
public Matrix4f getTranslationMatrix(float x, float y, float z) {
translationMatrix.m30 = x;
translationMatrix.m31 = y;
translationMatrix.m32 = z;
return translationMatrix;
}
此外,请检查您的 perspective
。我认为您在 ij
索引中有一些错误。 ii
细胞看起来不错。