Minecraft渲染原理(1)
上回说到, 渲染一帧(包括绘制世界和绘制UI)的主要逻辑发生在 net/minecraft/client/render/GameRenderer.java
中. 而实际上, GameRenderer.java
又将渲染世界的任务分包给了同一目录下的 WorldRenderer.java
.
GameRenderer.render
函数完成的主要工作罗列如下:
- 处理暂停相关逻辑, 必要时将
MinecraftClient.currentScreen
设置成暂停界面 - 根据屏幕尺寸设置视口
824
RenderSystem.viewport(0, 0, this.client.getWindow().getFramebufferWidth(), this.client.getWindow().getFramebufferHeight());
- 渲染世界
827
this.renderWorld(tickDelta, startTime, new MatrixStack());
- 将实体的轮廓线绘制到默认帧缓冲[1]
829
this.client.worldRenderer.drawEntityOutlinesFramebuffer();
- 进行后处理[2]
835
this.shader.render(tickDelta);
- 绑定
MinecraftClient.framebuffer
对应的 FBO837
this.client.getFramebuffer().beginWrite(true);
- 设置一番
RenderSystem
里的 MV 和 P 矩阵841
842
843
844
845
846Matrix4f matrix4f = Matrix4f.projectionMatrix(0.0f, (float)((double)window.getFramebufferWidth() / window.getScaleFactor()), 0.0f, (float)((double)window.getFramebufferHeight() / window.getScaleFactor()), 1000.0f, 3000.0f);
RenderSystem.setProjectionMatrix(matrix4f);
MatrixStack matrixStack = RenderSystem.getModelViewStack();
matrixStack.loadIdentity();
matrixStack.translate(0.0, 0.0, -2000.0);
RenderSystem.applyModelViewMatrix(); - 设置渲染 GUI 所需的一些 uniform (?), 似乎是为了产生光照效果
847
DiffuseLighting.enableGuiDepthLighting();
- 渲染反胃的特效(如果玩家处于相应状态的话)
853
this.renderNausea(f * (1.0f - this.client.options.distortionEffectScale));
- 绘制悬浮项[3]和 HUD
856
857this.renderFloatingItem(this.client.getWindow().getScaledWidth(), this.client.getWindow().getScaledHeight(), tickDelta);
this.client.inGameHud.render(matrixStack2, tickDelta); - 绘制叠加层[4]
864
this.client.getOverlay().render(matrixStack2, i, j, this.client.getLastFrameDuration());
- 绘制
MinecraftClient.currentScreen
875
this.client.currentScreen.render(matrixStack2, i, j, this.client.getLastFrameDuration());
点进 currentScreen
所属的类 Screen
可以发现, 其 render
方法有十万个甚至九万个重写, 例如 BeaconScreen
/BookScreen
/DeathScreen
等类都继承了 Screen
类并重写了该方法, 令人望而生畏. 根据这些子类的名字可以判断出, 绘制 currentScreen
就是绘制游戏中的各种菜单和界面, 如信标选择效果的界面/打开书的界面/死亡重生界面等.
发光实体/后处理/反胃/不死图腾/叠加层等特效都不是我们所关心的, 只有 GameRenderer.renderWorld
函数里的东西才是热衷于烧显卡的程序员的热爱之物.
GameRenderer.renderWorld
干的事情如下:
- 让
GameRenderer.lightmapTextureManager
进行某种更新. 鉴于根本没有正经光影包会管游戏里自带的光照图, 我们可以当这件事情不存在. - 设置相机依附的实体, 以合理更新后处理着色器(比如说蜘蛛)
978
979
980if (this.client.getCameraEntity() == null) {
this.client.setCameraEntity(this.client.player);
} - 更新十字叉丝瞄准的实体
981
this.updateTargetedEntity(tickDelta);
- 进行了一堆神必的矩阵操作以获得 P 矩阵
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005MatrixStack matrixStack = new MatrixStack();
double d = this.getFov(camera, tickDelta, true);
matrixStack.peek().getPositionMatrix().multiply(this.getBasicProjectionMatrix(d));
this.bobViewWhenHurt(matrixStack, tickDelta);
if (this.client.options.bobView) {
this.bobView(matrixStack, tickDelta);
}
if ((f = MathHelper.lerp(tickDelta, this.client.player.lastNauseaStrength, this.client.player.nextNauseaStrength) * (this.client.options.distortionEffectScale * this.client.options.distortionEffectScale)) > 0.0f) {
int i = this.client.player.hasStatusEffect(StatusEffects.NAUSEA) ? 7 : 20;
float g = 5.0f / (f * f + 5.0f) - f * 0.04f;
g *= g;
Vec3f vec3f = new Vec3f(0.0f, MathHelper.SQUARE_ROOT_OF_TWO / 2.0f, MathHelper.SQUARE_ROOT_OF_TWO / 2.0f);
matrixStack.multiply(vec3f.getDegreesQuaternion(((float)this.ticks + tickDelta) * (float)i));
matrixStack.scale(1.0f / g, 1.0f, 1.0f);
float h = -((float)this.ticks + tickDelta) * (float)i;
matrixStack.multiply(vec3f.getDegreesQuaternion(h));
}
Matrix4f matrix4f = matrixStack.peek().getPositionMatrix();
this.loadProjectionMatrix(matrix4f); - 更新摄像机的状态, 如依附的实体/旋转角/位置等量, 并进行了一堆神必的矩阵操作将摄像机的位姿加入 MV 矩阵
1006
1007
1008
1009
1010
1011
1012camera.update(this.client.world, this.client.getCameraEntity() == null ? this.client.player : this.client.getCameraEntity(), !this.client.options.getPerspective().isFirstPerson(), this.client.options.getPerspective().isFrontView(), tickDelta);
matrices.multiply(Vec3f.POSITIVE_X.getDegreesQuaternion(camera.getPitch()));
matrices.multiply(Vec3f.POSITIVE_Y.getDegreesQuaternion(camera.getYaw() + 180.0f));
Matrix3f matrix3f = matrices.peek().getNormalMatrix().copy();
if (matrix3f.invert()) {
RenderSystem.setInverseViewRotationMatrix(matrix3f);
} - 设置
WorldRenderer
的视锥体, 并进行渲染(要来力! (狂喜))1013
1014this.client.worldRenderer.setupFrustum(matrices, camera.getPos(), this.getBasicProjectionMatrix(Math.max(d, this.client.options.fov)));
this.client.worldRenderer.render(matrices, tickDelta, limitTime, bl, camera, this, this.lightmapTextureManager, matrix4f); - 绘制手(和手里拿着的物品)
1016
1017
1018
1019if (this.renderHand) {
RenderSystem.clear(256, MinecraftClient.IS_SYSTEM_MAC);
this.renderHand(matrices, camera, tickDelta);
}
欲知 WorldRenderer.render(MatrixStack ...)
如何, 且听下回分解.