diff --git a/src/main/java/ru/windcorp/optica/client/ClientProxy.java b/src/main/java/ru/windcorp/optica/client/ClientProxy.java index c0f1bcf..4df867b 100644 --- a/src/main/java/ru/windcorp/optica/client/ClientProxy.java +++ b/src/main/java/ru/windcorp/optica/client/ClientProxy.java @@ -21,8 +21,8 @@ import ru.windcorp.optica.Proxy; import ru.windcorp.optica.client.graphics.GUI; import ru.windcorp.optica.client.graphics.backend.GraphicsBackend; import ru.windcorp.optica.client.graphics.backend.RenderTaskQueue; -import ru.windcorp.optica.client.graphics.model.ShapeRenderProgram; import ru.windcorp.optica.client.graphics.world.LayerWorld; +import ru.windcorp.optica.client.graphics.world.WorldRenderProgram; public class ClientProxy implements Proxy { @@ -30,7 +30,7 @@ public class ClientProxy implements Proxy { public void initialize() { GraphicsBackend.initialize(); try { - RenderTaskQueue.waitAndInvoke(ShapeRenderProgram::init); + RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/EmptyModel.java b/src/main/java/ru/windcorp/optica/client/graphics/model/EmptyModel.java index 0fda90c..8e22707 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/model/EmptyModel.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/EmptyModel.java @@ -18,7 +18,6 @@ package ru.windcorp.optica.client.graphics.model; import glm.mat._4.Mat4; -import ru.windcorp.optica.client.graphics.world.WorldRenderer; public class EmptyModel extends Model { @@ -33,7 +32,7 @@ public class EmptyModel extends Model { } @Override - public void render(WorldRenderer renderer) { + public void render(ShapeRenderHelper helper) { // Do nothing } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/Face.java b/src/main/java/ru/windcorp/optica/client/graphics/model/Face.java index ec90680..353eb75 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/model/Face.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/Face.java @@ -21,7 +21,6 @@ import java.nio.ByteBuffer; import java.nio.ShortBuffer; import java.util.Objects; -import glm.vec._3.Vec3; import ru.windcorp.optica.client.graphics.texture.Texture; public class Face { @@ -63,39 +62,6 @@ public class Face { checkVertices(); checkIndices(); } - - void computeNormals() { - Vec3 a = new Vec3(); - Vec3 b = new Vec3(); - Vec3 c = new Vec3(); - Vec3 normal = new Vec3(); - - for (int i = 0; i < getIndexCount(); i += 3) { - int indexA = getIndex(i + 0); - int indexB = getIndex(i + 1); - int indexC = getIndex(i + 2); - - loadVertexPosition(indexA, a); - loadVertexPosition(indexB, b); - loadVertexPosition(indexC, c); - - computeOneNormal(a, b, c, normal); - - saveVertexNormal(indexA, normal); - saveVertexNormal(indexB, normal); - saveVertexNormal(indexC, normal); - } - } - - private void computeOneNormal( - Vec3 a, Vec3 b, Vec3 c, - Vec3 normal - ) { - b.sub(a); - c.sub(a); - b.cross(c, normal); - normal.normalize(); - } private void checkVertices() { if (vertices.remaining() % getBytesPerVertex() != 0) { @@ -143,6 +109,12 @@ public class Face { } } + public void markForVertexUpdate() { + if (shape != null) checkVertices(); + markShapeForReassembly(); + verticesUpdated = true; + } + boolean needsVerticesUpdate() { return verticesUpdated; } @@ -180,37 +152,9 @@ public class Face { return vertices; } - private void loadVertexPosition(int index, Vec3 result) { - int offset = vertices.position() + index * getBytesPerVertex(); - - result.set( - vertices.getFloat(offset + 0 * Float.BYTES), - vertices.getFloat(offset + 1 * Float.BYTES), - vertices.getFloat(offset + 2 * Float.BYTES) - ); - } - - private void saveVertexNormal(int index, Vec3 normal) { - int offset = vertices.position() + index * getBytesPerVertex() + ( - 3 * Float.BYTES + - 3 * Float.BYTES + - 2 * Float.BYTES - ); - - vertices.putFloat(offset + 0 * Float.BYTES, normal.x); - vertices.putFloat(offset + 1 * Float.BYTES, normal.y); - vertices.putFloat(offset + 2 * Float.BYTES, normal.z); - - verticesUpdated = true; - } - public Face setVertices(ByteBuffer vertices) { this.vertices = Objects.requireNonNull(vertices, "vertices"); - markShapeForReassembly(); - this.verticesUpdated = true; - - if (shape != null) checkVertices(); - + markForVertexUpdate(); return this; } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/Faces.java b/src/main/java/ru/windcorp/optica/client/graphics/model/Faces.java index 6c5d6c9..1d32beb 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/model/Faces.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/Faces.java @@ -30,13 +30,14 @@ public class Faces { private Faces() {} public static Face createRectangle( + ShapeRenderProgram program, Texture texture, Vec3 colorMultiplier, Vec3 origin, Vec3 width, Vec3 height ) { - VertexBuilder builder = new VertexBuilder(); + VertexBuilder builder = program.getVertexBuilder(); Vec3 pos = new Vec3(); Vec2 texCoords = new Vec2(); @@ -70,6 +71,7 @@ public class Faces { } public static Face createBlockFace( + ShapeRenderProgram program, Texture texture, Vec3 colorMultiplier, Vec3 blockCenter, @@ -78,6 +80,7 @@ public class Faces { switch (face) { case TOP: return createRectangle( + program, texture, colorMultiplier, blockCenter.add(-0.5f, +0.5f, +0.5f), new Vec3( 0, -1, 0), @@ -85,6 +88,7 @@ public class Faces { ); case BOTTOM: return createRectangle( + program, texture, colorMultiplier, blockCenter.add(-0.5f, -0.5f, -0.5f), new Vec3( 0, +1, 0), @@ -92,6 +96,7 @@ public class Faces { ); case NORTH: return createRectangle( + program, texture, colorMultiplier, blockCenter.add(+0.5f, -0.5f, -0.5f), new Vec3( 0, +1, 0), @@ -99,6 +104,7 @@ public class Faces { ); case SOUTH: return createRectangle( + program, texture, colorMultiplier, blockCenter.add(-0.5f, +0.5f, -0.5f), new Vec3( 0, -1, 0), @@ -106,6 +112,7 @@ public class Faces { ); case EAST: return createRectangle( + program, texture, colorMultiplier, blockCenter.add(-0.5f, -0.5f, -0.5f), new Vec3(+1, 0, 0), @@ -113,6 +120,7 @@ public class Faces { ); case WEST: return createRectangle( + program, texture, colorMultiplier, blockCenter.add(+0.5f, +0.5f, -0.5f), new Vec3(-1, 0, 0), diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/Model.java b/src/main/java/ru/windcorp/optica/client/graphics/model/Model.java index b65ed39..c2864c1 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/model/Model.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/Model.java @@ -18,7 +18,6 @@ package ru.windcorp.optica.client.graphics.model; import glm.mat._4.Mat4; -import ru.windcorp.optica.client.graphics.world.WorldRenderer; public abstract class Model implements WorldRenderable { @@ -31,16 +30,16 @@ public abstract class Model implements WorldRenderable { protected abstract Mat4 getTransform(int partIndex); @Override - public void render(WorldRenderer renderer) { + public void render(ShapeRenderHelper helper) { for (int i = 0; i < parts.length; ++i) { WorldRenderable part = parts[i]; Mat4 transform = getTransform(i); try { - renderer.pushWorldTransform().mul(transform); - part.render(renderer); + helper.pushWorldTransform().mul(transform); + part.render(helper); } finally { - renderer.popWorldTransform(); + helper.popWorldTransform(); } } } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/Shape.java b/src/main/java/ru/windcorp/optica/client/graphics/model/Shape.java index 05dba75..c733ba7 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/model/Shape.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/Shape.java @@ -24,7 +24,6 @@ import org.lwjgl.BufferUtils; import ru.windcorp.optica.client.graphics.backend.Usage; import ru.windcorp.optica.client.graphics.backend.VertexBufferObject; -import ru.windcorp.optica.client.graphics.world.WorldRenderer; public class Shape implements WorldRenderable { @@ -48,18 +47,14 @@ public class Shape implements WorldRenderable { this.usage = usage; configureFaces(); + program.preprocess(this); assembleBuffers(); } - public Shape(Usage usage, Face... faces) { - this(usage, ShapeRenderProgram.getDefault(), faces); - } - private void configureFaces() { for (Face face : faces) { face.setShape(this); - face.computeNormals(); } } @@ -153,12 +148,12 @@ public class Shape implements WorldRenderable { } @Override - public void render(WorldRenderer renderer) { + public void render(ShapeRenderHelper helper) { if (!initialized) initialize(); if (needsAssembly) assembleBuffers(); if (needsVBOUpdate) updateVBO(); - program.render(renderer, this); + program.render(helper, this); } private void initialize() { diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/ShapeRenderHelper.java b/src/main/java/ru/windcorp/optica/client/graphics/model/ShapeRenderHelper.java new file mode 100644 index 0000000..f01f716 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/ShapeRenderHelper.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.model; + +import glm.mat._4.Mat4; +import ru.windcorp.optica.common.util.StashingStack; + +public class ShapeRenderHelper { + + protected static final int TRANSFORM_STACK_SIZE = 64; + + private final StashingStack transformStack = new StashingStack<>( + TRANSFORM_STACK_SIZE, Mat4::new + ); + + { + transformStack.push().identity(); + } + + public Mat4 pushWorldTransform() { + Mat4 previous = transformStack.getHead(); + return transformStack.push().set(previous); + } + + public void popWorldTransform() { + transformStack.removeHead(); + } + + public Mat4 getWorldTransform() { + return transformStack.getHead(); + } + + public Mat4 getFinalTransform() { + return getWorldTransform(); + } + + public void reset() { + transformStack.removeAll(); + transformStack.push().identity(); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/ShapeRenderProgram.java b/src/main/java/ru/windcorp/optica/client/graphics/model/ShapeRenderProgram.java index e0560ca..c695f70 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/model/ShapeRenderProgram.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/ShapeRenderProgram.java @@ -32,33 +32,16 @@ import ru.windcorp.optica.client.graphics.backend.VertexBufferObject; import ru.windcorp.optica.client.graphics.backend.VertexBufferObject.BindTarget; import ru.windcorp.optica.client.graphics.backend.shaders.CombinedShader; import ru.windcorp.optica.client.graphics.backend.shaders.Program; -import ru.windcorp.optica.client.graphics.backend.shaders.attributes.AttributeVertexArray; -import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.Uniform1Int; -import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.Uniform2Float; -import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.Uniform4Matrix; +import ru.windcorp.optica.client.graphics.backend.shaders.attributes.*; +import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.*; import ru.windcorp.optica.client.graphics.texture.Sprite; -import ru.windcorp.optica.client.graphics.world.WorldRenderer; public class ShapeRenderProgram extends Program { - private static ShapeRenderProgram def = null; - - public static void init() { - def = new ShapeRenderProgram( - new String[] {"WorldDefault.vertex.glsl"}, - new String[] {"WorldDefault.fragment.glsl"} - ); - } - - public static ShapeRenderProgram getDefault() { - return def; - } - private static final int DEFAULT_BYTES_PER_VERTEX = 3 * Float.BYTES + // Position 3 * Float.BYTES + // Color multiplier - 2 * Float.BYTES + // Texture coordinates - 3 * Float.BYTES; // Normals + 2 * Float.BYTES; // Texture coordinates private static final String SHAPE_VERTEX_SHADER_RESOURCE = "Shape.vertex.glsl"; @@ -67,24 +50,20 @@ public class ShapeRenderProgram extends Program { private static final String FINAL_TRANSFORM_UNIFORM_NAME = "finalTransform", - WORLD_TRANSFORM_UNIFORM_NAME = "worldTransform", POSITIONS_ATTRIBUTE_NAME = "inputPositions", COLOR_MULTIPLER_ATTRIBUTE_NAME = "inputColorMultiplier", TEXTURE_COORDS_ATTRIBUTE_NAME = "inputTextureCoords", TEXTURE_SLOT_UNIFORM_NAME = "textureSlot", TEXTURE_START_UNIFORM_NAME = "textureStart", - TEXTURE_SIZE_UNIFORM_NAME = "textureSize", - NORMALS_ATTRIBUTE_NAME = "inputNormals"; + TEXTURE_SIZE_UNIFORM_NAME = "textureSize"; private final Uniform4Matrix finalTransformUniform; - private final Uniform4Matrix worldTransformUniform; private final AttributeVertexArray positionsAttribute; private final AttributeVertexArray colorsAttribute; private final AttributeVertexArray textureCoordsAttribute; private final Uniform1Int textureSlotUniform; private final Uniform2Float textureStartUniform; private final Uniform2Float textureSizeUniform; - private final AttributeVertexArray normalsAttribute; public ShapeRenderProgram( String[] vertexShaderResources, @@ -102,9 +81,6 @@ public class ShapeRenderProgram extends Program { this.finalTransformUniform = getUniform(FINAL_TRANSFORM_UNIFORM_NAME) .as4Matrix(); - this.worldTransformUniform = getUniform(WORLD_TRANSFORM_UNIFORM_NAME) - .as4Matrix(); - this.positionsAttribute = getAttribute(POSITIONS_ATTRIBUTE_NAME).asVertexArray(); @@ -122,9 +98,6 @@ public class ShapeRenderProgram extends Program { this.textureSizeUniform = getUniform(TEXTURE_SIZE_UNIFORM_NAME) .as2Float(); - - this.normalsAttribute = getAttribute(NORMALS_ATTRIBUTE_NAME) - .asVertexArray(); } private static String[] attachVertexShader(String[] others) { @@ -136,36 +109,39 @@ public class ShapeRenderProgram extends Program { } public void render( - WorldRenderer renderer, + ShapeRenderHelper helper, Shape shape ) { use(); - configure(renderer); + configure(helper); bindVertices(shape.getVerticesVbo()); bindIndices(shape.getIndicesVbo()); try { - positionsAttribute.enable(); - colorsAttribute.enable(); - textureCoordsAttribute.enable(); - normalsAttribute.enable(); - + enableAttributes(); for (Face face : shape.getFaces()) { renderFace(face); } - } finally { - positionsAttribute.disable(); - colorsAttribute.disable(); - textureCoordsAttribute.disable(); - normalsAttribute.disable(); + disableAttributes(); } } - protected void configure(WorldRenderer renderer) { - finalTransformUniform.set(renderer.getFinalTransform()); - worldTransformUniform.set(renderer.getWorldTransform()); + protected void enableAttributes() { + positionsAttribute.enable(); + colorsAttribute.enable(); + textureCoordsAttribute.enable(); + } + + protected void disableAttributes() { + positionsAttribute.disable(); + colorsAttribute.disable(); + textureCoordsAttribute.disable(); + } + + protected void configure(ShapeRenderHelper helper) { + finalTransformUniform.set(helper.getFinalTransform()); } protected int bindVertices(VertexBufferObject vertices) { @@ -190,12 +166,6 @@ public class ShapeRenderProgram extends Program { ); offset += 2 * Float.BYTES; - normalsAttribute.set( - 3, GL11.GL_FLOAT, false, vertexStride, vertices, - offset - ); - offset += 3 * Float.BYTES; - return offset; } @@ -224,7 +194,31 @@ public class ShapeRenderProgram extends Program { return DEFAULT_BYTES_PER_VERTEX; } - public static class VertexBuilder { + public void preprocess(Shape shape) { + // To be overridden + } + + public VertexBuilder getVertexBuilder() { + return new SRPVertexBuilder(); + } + + public static interface VertexBuilder { + VertexBuilder addVertex( + float x, float y, float z, + float r, float g, float b, + float tx, float ty + ); + + VertexBuilder addVertex( + Vec3 position, + Vec3 colorMultiplier, + Vec2 textureCoords + ); + + ByteBuffer assemble(); + } + + public static class SRPVertexBuilder implements VertexBuilder { private static class Vertex { final Vec3 position; @@ -282,10 +276,7 @@ public class ShapeRenderProgram extends Program { .putFloat(v.colorMultiplier.y) .putFloat(v.colorMultiplier.z) .putFloat(v.textureCoords.x) - .putFloat(v.textureCoords.y) - .putFloat(Float.NaN) - .putFloat(Float.NaN) - .putFloat(Float.NaN); + .putFloat(v.textureCoords.y); } result.flip(); diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/Shapes.java b/src/main/java/ru/windcorp/optica/client/graphics/model/Shapes.java index 54dac98..9da8fbe 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/model/Shapes.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/Shapes.java @@ -24,6 +24,8 @@ import ru.windcorp.optica.client.graphics.texture.Texture; public class Shapes { public static Shape createParallelepiped( // Try saying that 10 times fast + ShapeRenderProgram program, + Vec3 origin, Vec3 width, @@ -44,6 +46,7 @@ public class Shapes { Vec3 faceWidth = new Vec3(); Face top = Faces.createRectangle( + program, topTexture, colorMultiplier, faceOrigin.set(origin).add(height).add(width), faceWidth.set(width).negate(), @@ -51,6 +54,7 @@ public class Shapes { ); Face bottom = Faces.createRectangle( + program, bottomTexture, colorMultiplier, origin, width, @@ -58,6 +62,7 @@ public class Shapes { ); Face north = Faces.createRectangle( + program, northTexture, colorMultiplier, faceOrigin.set(origin).add(depth), width, @@ -65,6 +70,7 @@ public class Shapes { ); Face south = Faces.createRectangle( + program, southTexture, colorMultiplier, faceOrigin.set(origin).add(width), faceWidth.set(width).negate(), @@ -72,6 +78,7 @@ public class Shapes { ); Face east = Faces.createRectangle( + program, eastTexture, colorMultiplier, origin, depth, @@ -79,6 +86,7 @@ public class Shapes { ); Face west = Faces.createRectangle( + program, westTexture, colorMultiplier, faceOrigin.set(origin).add(width).add(depth), faceWidth.set(depth).negate(), @@ -87,6 +95,7 @@ public class Shapes { Shape result = new Shape( Usage.STATIC, + program, top, bottom, north, south, east, west ); @@ -95,6 +104,8 @@ public class Shapes { public static class PppBuilder { + private final ShapeRenderProgram program; + private final Vec3 origin = new Vec3(-0.5f, -0.5f, -0.5f); private final Vec3 depth = new Vec3(1, 0, 0); @@ -111,6 +122,7 @@ public class Shapes { private final Texture westTexture; public PppBuilder( + ShapeRenderProgram program, Texture top, Texture bottom, Texture north, @@ -118,6 +130,7 @@ public class Shapes { Texture east, Texture west ) { + this.program = program; this.topTexture = top; this.bottomTexture = bottom; this.northTexture = north; @@ -126,8 +139,8 @@ public class Shapes { this.westTexture = west; } - public PppBuilder(Texture texture) { - this(texture, texture, texture, texture, texture, texture); + public PppBuilder(ShapeRenderProgram program, Texture texture) { + this(program, texture, texture, texture, texture, texture, texture); } public PppBuilder setOrigin(Vec3 origin) { @@ -201,6 +214,7 @@ public class Shapes { public Shape create() { return createParallelepiped( + program, origin, width, height, depth, colorMultiplier, diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/WorldRenderable.java b/src/main/java/ru/windcorp/optica/client/graphics/model/WorldRenderable.java index e8fbe90..d4333a0 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/model/WorldRenderable.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/WorldRenderable.java @@ -17,10 +17,8 @@ *******************************************************************************/ package ru.windcorp.optica.client.graphics.model; -import ru.windcorp.optica.client.graphics.world.WorldRenderer; - public interface WorldRenderable { - void render(WorldRenderer renderer); + void render(ShapeRenderHelper renderer); } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/texture/Pixels.java b/src/main/java/ru/windcorp/optica/client/graphics/texture/Pixels.java index 9ff3b2f..8f28c38 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/texture/Pixels.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/texture/Pixels.java @@ -75,8 +75,6 @@ class Pixels { data // Data buffer ); - glBindTexture(GL_TEXTURE_2D, 0); - return handle; } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/world/Camera.java b/src/main/java/ru/windcorp/optica/client/graphics/world/Camera.java index 071575e..5ce57f1 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/world/Camera.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/world/Camera.java @@ -42,18 +42,18 @@ public class Camera { public Camera() {} - public void apply(WorldRenderer renderer) { - Mat4 previous = renderer.getViewTransform(); + public void apply(WorldRenderHelper helper) { + Mat4 previous = helper.getViewTransform(); Glm.perspective( computeFovY(), GraphicsInterface.getAspectRatio(), 0.01f, 10000.0f, - renderer.pushViewTransform() + helper.pushViewTransform() ).mul(previous); - renderer.pushViewTransform().rotateX(pitch).rotateY(yaw); + helper.pushViewTransform().rotateX(pitch).rotateY(yaw); - renderer.pushViewTransform().translate(position.negate()); + helper.pushViewTransform().translate(position.negate()); position.negate(); } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/world/LayerWorld.java b/src/main/java/ru/windcorp/optica/client/graphics/world/LayerWorld.java index e086e60..59d5bad 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/world/LayerWorld.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/world/LayerWorld.java @@ -50,7 +50,10 @@ public class LayerWorld extends Layer { private int movementY = 0; private int movementZ = 0; - private final WorldRenderer renderer = new WorldRenderer(); + private static final boolean I_WANT_TO_THROW_UP = false; + private float shakeParam = 0; + + private final WorldRenderHelper helper = new WorldRenderHelper(); private final WorldRender world = new WorldRender(new WorldData()); @@ -66,13 +69,14 @@ public class LayerWorld extends Layer { @Override protected void doRender() { - camera.apply(renderer); + camera.apply(helper); renderWorld(); - renderer.reset(); + helper.reset(); angMat.set().rotateY(-camera.getYaw()); tmp.set(movementX, 0, movementZ); + if (movementX != 0 && movementZ != 0) tmp.normalize(); angMat.mul_(tmp); // bug in jglm tmp.y = movementY; tmp.mul(0.1f); @@ -82,10 +86,23 @@ public class LayerWorld extends Layer { tmp.set(velocity); tmp.mul((float) (GraphicsInterface.getFrameLength() * 60)); camera.move(tmp); + + if (I_WANT_TO_THROW_UP) { + shakeParam += tmp.set(tmp.x, 0, tmp.z).length() * 1.5f; + float vel = tmp.set(velocity).set(tmp.x, 0, tmp.z).length() * 0.7f; + + helper.pushViewTransform().translate( + (float) Math.sin(shakeParam) * vel, + (float) Math.sin(2 * shakeParam) * vel, + 0.25f + ).rotateZ( + (float) Math.sin(shakeParam) * vel * 0.15f + ); + } } private void renderWorld() { - world.render(renderer); + world.render(helper); } public Camera getCamera() { diff --git a/src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderer.java b/src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderHelper.java similarity index 71% rename from src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderer.java rename to src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderHelper.java index 1733b4f..5caefd9 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderer.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderHelper.java @@ -18,38 +18,20 @@ package ru.windcorp.optica.client.graphics.world; import glm.mat._4.Mat4; +import ru.windcorp.optica.client.graphics.model.ShapeRenderHelper; import ru.windcorp.optica.common.util.StashingStack; -public class WorldRenderer { - - private static final int TRANSFORM_STACK_SIZE = 64; - - private final StashingStack worldTransformStack = new StashingStack<>( - TRANSFORM_STACK_SIZE, Mat4::new - ); +public class WorldRenderHelper extends ShapeRenderHelper { private final StashingStack viewTransformStack = new StashingStack<>( TRANSFORM_STACK_SIZE, Mat4::new ); - private final Mat4 finalTransform = new Mat4(); - { - reset(); + viewTransformStack.push().identity(); } - public Mat4 pushWorldTransform() { - Mat4 previous = worldTransformStack.getHead(); - return worldTransformStack.push().set(previous); - } - - public void popWorldTransform() { - worldTransformStack.removeHead(); - } - - public Mat4 getWorldTransform() { - return worldTransformStack.getHead(); - } + private final Mat4 finalTransform = new Mat4(); public Mat4 pushViewTransform() { Mat4 previous = viewTransformStack.getHead(); @@ -64,13 +46,14 @@ public class WorldRenderer { return viewTransformStack.getHead(); } + @Override public Mat4 getFinalTransform() { return finalTransform.set(getViewTransform()).mul(getWorldTransform()); } + @Override public void reset() { - worldTransformStack.removeAll(); - worldTransformStack.push().identity(); + super.reset(); viewTransformStack.removeAll(); viewTransformStack.push().identity(); } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderProgram.java b/src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderProgram.java new file mode 100644 index 0000000..baac644 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderProgram.java @@ -0,0 +1,283 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.world; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; + +import com.google.common.collect.ObjectArrays; + +import glm.vec._2.Vec2; +import glm.vec._3.Vec3; +import ru.windcorp.optica.client.graphics.backend.VertexBufferObject; +import ru.windcorp.optica.client.graphics.backend.shaders.attributes.*; +import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.*; +import ru.windcorp.optica.client.graphics.model.Face; +import ru.windcorp.optica.client.graphics.model.Shape; +import ru.windcorp.optica.client.graphics.model.ShapeRenderHelper; +import ru.windcorp.optica.client.graphics.model.ShapeRenderProgram; + +public class WorldRenderProgram extends ShapeRenderProgram { + + private static WorldRenderProgram def = null; + + public static void init() { + def = new WorldRenderProgram( + new String[] {"WorldDefault.vertex.glsl"}, + new String[] {"WorldDefault.fragment.glsl"} + ); + } + + public static WorldRenderProgram getDefault() { + return def; + } + + private static final int DEFAULT_BYTES_PER_VERTEX = + 3 * Float.BYTES + // Position + 3 * Float.BYTES + // Color multiplier + 2 * Float.BYTES + // Texture coordinates + 3 * Float.BYTES; // Normals + + private static final String WORLD_VERTEX_SHADER_RESOURCE = + "World.vertex.glsl"; + private static final String WORLD_FRAGMENT_SHADER_RESOURCE = + "World.fragment.glsl"; + + private static final String + WORLD_TRANSFORM_UNIFORM_NAME = "worldTransform", + NORMALS_ATTRIBUTE_NAME = "inputNormals"; + + private final Uniform4Matrix worldTransformUniform; + private final AttributeVertexArray normalsAttribute; + + public WorldRenderProgram( + String[] vertexShaderResources, + String[] fragmentShaderResources + ) { + super( + attachVertexShader(vertexShaderResources), + attachFragmentShader(fragmentShaderResources) + ); + + this.worldTransformUniform = getUniform(WORLD_TRANSFORM_UNIFORM_NAME) + .as4Matrix(); + + this.normalsAttribute = getAttribute(NORMALS_ATTRIBUTE_NAME) + .asVertexArray(); + } + + private static String[] attachVertexShader(String[] others) { + return ObjectArrays.concat(WORLD_VERTEX_SHADER_RESOURCE, others); + } + + private static String[] attachFragmentShader(String[] others) { + return ObjectArrays.concat(WORLD_FRAGMENT_SHADER_RESOURCE, others); + } + + @Override + protected void configure(ShapeRenderHelper helper) { + super.configure(helper); + worldTransformUniform.set(helper.getWorldTransform()); + } + + @Override + protected int bindVertices(VertexBufferObject vertices) { + int vertexStride = getBytesPerVertex(); + int offset = super.bindVertices(vertices); + + normalsAttribute.set( + 3, GL11.GL_FLOAT, false, vertexStride, vertices, + offset + ); + offset += 3 * Float.BYTES; + + return offset; + } + + @Override + protected void enableAttributes() { + super.enableAttributes(); + normalsAttribute.enable(); + } + + @Override + protected void disableAttributes() { + super.disableAttributes(); + normalsAttribute.disable(); + } + + @Override + public int getBytesPerVertex() { + return super.getBytesPerVertex() + + 3 * Float.BYTES; // Normals + } + + @Override + public void preprocess(Shape shape) { + super.preprocess(shape); + + for (Face face : shape.getFaces()) { + computeNormals(face); + } + } + + private void computeNormals(Face face) { + Vec3 a = new Vec3(); + Vec3 b = new Vec3(); + Vec3 c = new Vec3(); + Vec3 normal = new Vec3(); + + for (int i = 0; i < face.getIndexCount(); i += 3) { + int indexA = face.getIndex(i + 0); + int indexB = face.getIndex(i + 1); + int indexC = face.getIndex(i + 2); + + loadVertexPosition(face, indexA, a); + loadVertexPosition(face, indexB, b); + loadVertexPosition(face, indexC, c); + + computeOneNormal(a, b, c, normal); + + saveVertexNormal(face, indexA, normal); + saveVertexNormal(face, indexB, normal); + saveVertexNormal(face, indexC, normal); + } + } + + private void computeOneNormal( + Vec3 a, Vec3 b, Vec3 c, + Vec3 normal + ) { + b.sub(a); + c.sub(a); + b.cross(c, normal); + normal.normalize(); + } + + private void loadVertexPosition(Face face, int index, Vec3 result) { + ByteBuffer vertices = face.getVertices(); + int offset = vertices.position() + index * getBytesPerVertex(); + + result.set( + vertices.getFloat(offset + 0 * Float.BYTES), + vertices.getFloat(offset + 1 * Float.BYTES), + vertices.getFloat(offset + 2 * Float.BYTES) + ); + } + + private void saveVertexNormal(Face face, int index, Vec3 normal) { + ByteBuffer vertices = face.getVertices(); + int offset = vertices.position() + index * getBytesPerVertex() + ( + 3 * Float.BYTES + + 3 * Float.BYTES + + 2 * Float.BYTES + ); + + vertices.putFloat(offset + 0 * Float.BYTES, normal.x); + vertices.putFloat(offset + 1 * Float.BYTES, normal.y); + vertices.putFloat(offset + 2 * Float.BYTES, normal.z); + + face.markForVertexUpdate(); + } + + @Override + public VertexBuilder getVertexBuilder() { + return new WRPVertexBuilder(); + } + + private static class WRPVertexBuilder implements VertexBuilder { + // TODO Throw VertexBuilders out the window and rewrite completely. + // I want to _extend_ VBs, not re-implement them for children of SRP + + private static class Vertex { + final Vec3 position; + final Vec3 colorMultiplier; + final Vec2 textureCoords; + + Vertex(Vec3 position, Vec3 colorMultiplier, Vec2 textureCoords) { + this.position = position; + this.colorMultiplier = colorMultiplier; + this.textureCoords = textureCoords; + } + } + + private final List vertices = new ArrayList<>(); + + @Override + public VertexBuilder addVertex( + float x, float y, float z, + float r, float g, float b, + float tx, float ty + ) { + vertices.add(new Vertex( + new Vec3(x, y, z), + new Vec3(r, g, b), + new Vec2(tx, ty) + )); + + return this; + } + + @Override + public VertexBuilder addVertex( + Vec3 position, + Vec3 colorMultiplier, + Vec2 textureCoords + ) { + vertices.add(new Vertex( + new Vec3(position), + new Vec3(colorMultiplier), + new Vec2(textureCoords) + )); + + return this; + } + + @Override + public ByteBuffer assemble() { + ByteBuffer result = BufferUtils.createByteBuffer( + DEFAULT_BYTES_PER_VERTEX * vertices.size() + ); + + for (Vertex v : vertices) { + result + .putFloat(v.position.x) + .putFloat(v.position.y) + .putFloat(v.position.z) + .putFloat(v.colorMultiplier.x) + .putFloat(v.colorMultiplier.y) + .putFloat(v.colorMultiplier.z) + .putFloat(v.textureCoords.x) + .putFloat(v.textureCoords.y) + .putFloat(Float.NaN) + .putFloat(Float.NaN) + .putFloat(Float.NaN); + } + + result.flip(); + + return result; + } + + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/ChunkRender.java b/src/main/java/ru/windcorp/optica/client/world/ChunkRender.java index 1e166b0..53cfe27 100644 --- a/src/main/java/ru/windcorp/optica/client/world/ChunkRender.java +++ b/src/main/java/ru/windcorp/optica/client/world/ChunkRender.java @@ -26,7 +26,7 @@ import ru.windcorp.optica.client.graphics.model.Shape; import ru.windcorp.optica.client.graphics.model.StaticModel; import ru.windcorp.optica.client.graphics.model.StaticModel.Builder; import ru.windcorp.optica.client.graphics.model.WorldRenderable; -import ru.windcorp.optica.client.graphics.world.WorldRenderer; +import ru.windcorp.optica.client.graphics.model.ShapeRenderHelper; import ru.windcorp.optica.client.world.renders.BlockRender; import ru.windcorp.optica.client.world.renders.BlockRenderNone; import ru.windcorp.optica.client.world.renders.BlockRenders; @@ -70,7 +70,7 @@ public class ChunkRender { ); } - public void render(WorldRenderer renderer) { + public void render(ShapeRenderHelper renderer) { if (model == null || needsUpdate()) { buildModel(); } diff --git a/src/main/java/ru/windcorp/optica/client/world/WorldRender.java b/src/main/java/ru/windcorp/optica/client/world/WorldRender.java index 46efe7c..67301c9 100644 --- a/src/main/java/ru/windcorp/optica/client/world/WorldRender.java +++ b/src/main/java/ru/windcorp/optica/client/world/WorldRender.java @@ -21,7 +21,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; -import ru.windcorp.optica.client.graphics.world.WorldRenderer; +import ru.windcorp.optica.client.graphics.model.ShapeRenderHelper; import ru.windcorp.optica.common.world.ChunkData; import ru.windcorp.optica.common.world.WorldData; @@ -55,7 +55,7 @@ public class WorldRender { return chunks.values(); } - public void render(WorldRenderer renderer) { + public void render(ShapeRenderHelper renderer) { renderer.pushWorldTransform().rotateX(-Math.PI / 2); for (ChunkRender chunk : getChunks()) { diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/BlockRender.java b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRender.java index ca5731e..faadf63 100644 --- a/src/main/java/ru/windcorp/optica/client/world/renders/BlockRender.java +++ b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRender.java @@ -18,7 +18,7 @@ package ru.windcorp.optica.client.world.renders; import ru.windcorp.optica.client.graphics.model.WorldRenderable; -import ru.windcorp.optica.client.graphics.world.WorldRenderer; +import ru.windcorp.optica.client.graphics.model.ShapeRenderHelper; import ru.windcorp.optica.common.util.Namespaced; public abstract class BlockRender extends Namespaced { @@ -41,7 +41,7 @@ public abstract class BlockRender extends Namespaced { this.optimizer = optimizer; } - public void render(WorldRenderer renderer) { + public void render(ShapeRenderHelper renderer) { throw new UnsupportedOperationException( "BlockRender.render() not implemented" ); diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderTexturedCube.java b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderTexturedCube.java index f555c88..4359882 100644 --- a/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderTexturedCube.java +++ b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderTexturedCube.java @@ -24,6 +24,7 @@ import java.util.EnumMap; import ru.windcorp.optica.client.graphics.model.Shapes; import ru.windcorp.optica.client.graphics.model.WorldRenderable; import ru.windcorp.optica.client.graphics.texture.Texture; +import ru.windcorp.optica.client.graphics.world.WorldRenderProgram; import ru.windcorp.optica.common.block.BlockFace; public abstract class BlockRenderTexturedCube extends BlockRender { @@ -54,6 +55,7 @@ public abstract class BlockRenderTexturedCube extends BlockRender { @Override public WorldRenderable createRenderable() { return new Shapes.PppBuilder( + WorldRenderProgram.getDefault(), getTexture(TOP), getTexture(BOTTOM), getTexture(NORTH), getTexture(SOUTH), getTexture(EAST), getTexture(WEST) diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOpaqueCubeOptimizer.java b/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOpaqueCubeOptimizer.java index 5450456..002e160 100644 --- a/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOpaqueCubeOptimizer.java +++ b/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOpaqueCubeOptimizer.java @@ -28,6 +28,7 @@ import ru.windcorp.optica.client.graphics.model.Face; import ru.windcorp.optica.client.graphics.model.Faces; import ru.windcorp.optica.client.graphics.model.Shape; import ru.windcorp.optica.client.graphics.texture.Texture; +import ru.windcorp.optica.client.graphics.world.WorldRenderProgram; import ru.windcorp.optica.client.world.ChunkRender; import ru.windcorp.optica.client.world.renders.BlockRender; import ru.windcorp.optica.common.block.BlockFace; @@ -167,6 +168,7 @@ public class BlockRenderOpaqueCubeOptimizer extends BlockRenderOptimizer { return new Shape( Usage.STATIC, + WorldRenderProgram.getDefault(), shapeFaces.toArray(new Face[shapeFaces.size()]) ); } @@ -177,6 +179,7 @@ public class BlockRenderOpaqueCubeOptimizer extends BlockRenderOptimizer { Texture texture = blockRender.getTexture(face); return Faces.createBlockFace( + WorldRenderProgram.getDefault(), texture, COLOR_MULTIPLIER, blockCenter.set(x, y, z), diff --git a/src/main/resources/assets/shaders/Shape.fragment.glsl b/src/main/resources/assets/shaders/Shape.fragment.glsl index b80887c..d5e69b5 100644 --- a/src/main/resources/assets/shaders/Shape.fragment.glsl +++ b/src/main/resources/assets/shaders/Shape.fragment.glsl @@ -2,7 +2,6 @@ varying vec3 varyingColorMultiplier; varying vec2 varyingTextureCoords; -varying vec3 varyingNormals; uniform sampler2D textureSlot; uniform vec2 textureStart; @@ -36,16 +35,6 @@ void applyColorMultiplier() { linearMultiply(gl_FragColor, vec4(varyingColorMultiplier, 1.0)); } -void applyShading() { - vec3 light = normalize(vec3(0.5, 1.0, 0.2)); - vec3 normal = varyingNormals; - - float angleCos = dot(normal, light); - float lightness = (angleCos + 1.5) / 2; - - linearMultiply(gl_FragColor, vec4(lightness, lightness, lightness, 1.0)); -} - void applyAlpha() { if (gl_FragColor.w < 0.01) { discard; diff --git a/src/main/resources/assets/shaders/Shape.vertex.glsl b/src/main/resources/assets/shaders/Shape.vertex.glsl index 3ce95ff..b5da67f 100644 --- a/src/main/resources/assets/shaders/Shape.vertex.glsl +++ b/src/main/resources/assets/shaders/Shape.vertex.glsl @@ -8,20 +8,13 @@ varying vec3 varyingColorMultiplier; attribute vec2 inputTextureCoords; varying vec2 varyingTextureCoords; -attribute vec3 inputNormals; -varying vec3 varyingNormals; - -uniform mat4 worldTransform; uniform mat4 finalTransform; vec4 applyFinalTransform(vec4 vector) { return finalTransform * vector; } -void transferToFragment() { +void shapeTransferToFragment() { varyingColorMultiplier = inputColorMultiplier; varyingTextureCoords = inputTextureCoords; - - mat3 worldRotation = mat3(worldTransform); - varyingNormals = normalize(worldRotation * inputNormals); } \ No newline at end of file diff --git a/src/main/resources/assets/shaders/World.fragment.glsl b/src/main/resources/assets/shaders/World.fragment.glsl new file mode 100644 index 0000000..7ec061b --- /dev/null +++ b/src/main/resources/assets/shaders/World.fragment.glsl @@ -0,0 +1,13 @@ +#version 120 + +varying vec3 varyingNormals; + +void applyShading() { + vec3 light = normalize(vec3(0.5, 1.0, 0.2)); + vec3 normal = varyingNormals; + + float angleCos = dot(normal, light); + float lightness = (angleCos + 1.5) / 2; + + linearMultiply(gl_FragColor, vec4(lightness, lightness, lightness, 1.0)); +} \ No newline at end of file diff --git a/src/main/resources/assets/shaders/World.vertex.glsl b/src/main/resources/assets/shaders/World.vertex.glsl new file mode 100644 index 0000000..229df49 --- /dev/null +++ b/src/main/resources/assets/shaders/World.vertex.glsl @@ -0,0 +1,13 @@ +#version 120 + +attribute vec3 inputNormals; +varying vec3 varyingNormals; + +uniform mat4 worldTransform; + +void worldTransferToFragment() { + shapeTransferToFragment(); + + mat3 worldRotation = mat3(worldTransform); + varyingNormals = normalize(worldRotation * inputNormals); +} \ No newline at end of file diff --git a/src/main/resources/assets/shaders/WorldDefault.vertex.glsl b/src/main/resources/assets/shaders/WorldDefault.vertex.glsl index 11faad1..e9b6579 100644 --- a/src/main/resources/assets/shaders/WorldDefault.vertex.glsl +++ b/src/main/resources/assets/shaders/WorldDefault.vertex.glsl @@ -2,5 +2,5 @@ void main(void) { gl_Position = applyFinalTransform(vec4(inputPositions, 1.0)); - transferToFragment(); + worldTransferToFragment(); } \ No newline at end of file