Refactored ShapeRenderProgram into SRP and WorldRenderProgram

- ShapeRP now handles positions, textures and color multipliers only,
normals, view transforms and light calculations are now in WorldRP
- Renamed WorldRenderer to ShapeRenderHelper
- Moved normal computation to WorldRP code from Face
- No default ShapeRP exists, methods in Faces and Shapes now explicitly
require a program
- Removed unnecessary glBind from texture loader
- Refactored and renamed GLSL code
- Added a view bobbing test, enable with I_WANT_TO_THROW_UP in
LayerWorld
This commit is contained in:
OLEGSHA 2020-08-01 00:24:38 +03:00
parent 16205909ed
commit 8aa0bb8aa0
25 changed files with 503 additions and 204 deletions

View File

@ -21,8 +21,8 @@ import ru.windcorp.optica.Proxy;
import ru.windcorp.optica.client.graphics.GUI; import ru.windcorp.optica.client.graphics.GUI;
import ru.windcorp.optica.client.graphics.backend.GraphicsBackend; import ru.windcorp.optica.client.graphics.backend.GraphicsBackend;
import ru.windcorp.optica.client.graphics.backend.RenderTaskQueue; 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.LayerWorld;
import ru.windcorp.optica.client.graphics.world.WorldRenderProgram;
public class ClientProxy implements Proxy { public class ClientProxy implements Proxy {
@ -30,7 +30,7 @@ public class ClientProxy implements Proxy {
public void initialize() { public void initialize() {
GraphicsBackend.initialize(); GraphicsBackend.initialize();
try { try {
RenderTaskQueue.waitAndInvoke(ShapeRenderProgram::init); RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();

View File

@ -18,7 +18,6 @@
package ru.windcorp.optica.client.graphics.model; package ru.windcorp.optica.client.graphics.model;
import glm.mat._4.Mat4; import glm.mat._4.Mat4;
import ru.windcorp.optica.client.graphics.world.WorldRenderer;
public class EmptyModel extends Model { public class EmptyModel extends Model {
@ -33,7 +32,7 @@ public class EmptyModel extends Model {
} }
@Override @Override
public void render(WorldRenderer renderer) { public void render(ShapeRenderHelper helper) {
// Do nothing // Do nothing
} }

View File

@ -21,7 +21,6 @@ import java.nio.ByteBuffer;
import java.nio.ShortBuffer; import java.nio.ShortBuffer;
import java.util.Objects; import java.util.Objects;
import glm.vec._3.Vec3;
import ru.windcorp.optica.client.graphics.texture.Texture; import ru.windcorp.optica.client.graphics.texture.Texture;
public class Face { public class Face {
@ -64,39 +63,6 @@ public class Face {
checkIndices(); 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() { private void checkVertices() {
if (vertices.remaining() % getBytesPerVertex() != 0) { if (vertices.remaining() % getBytesPerVertex() != 0) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -143,6 +109,12 @@ public class Face {
} }
} }
public void markForVertexUpdate() {
if (shape != null) checkVertices();
markShapeForReassembly();
verticesUpdated = true;
}
boolean needsVerticesUpdate() { boolean needsVerticesUpdate() {
return verticesUpdated; return verticesUpdated;
} }
@ -180,37 +152,9 @@ public class Face {
return vertices; 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) { public Face setVertices(ByteBuffer vertices) {
this.vertices = Objects.requireNonNull(vertices, "vertices"); this.vertices = Objects.requireNonNull(vertices, "vertices");
markShapeForReassembly(); markForVertexUpdate();
this.verticesUpdated = true;
if (shape != null) checkVertices();
return this; return this;
} }

View File

@ -30,13 +30,14 @@ public class Faces {
private Faces() {} private Faces() {}
public static Face createRectangle( public static Face createRectangle(
ShapeRenderProgram program,
Texture texture, Texture texture,
Vec3 colorMultiplier, Vec3 colorMultiplier,
Vec3 origin, Vec3 origin,
Vec3 width, Vec3 width,
Vec3 height Vec3 height
) { ) {
VertexBuilder builder = new VertexBuilder(); VertexBuilder builder = program.getVertexBuilder();
Vec3 pos = new Vec3(); Vec3 pos = new Vec3();
Vec2 texCoords = new Vec2(); Vec2 texCoords = new Vec2();
@ -70,6 +71,7 @@ public class Faces {
} }
public static Face createBlockFace( public static Face createBlockFace(
ShapeRenderProgram program,
Texture texture, Texture texture,
Vec3 colorMultiplier, Vec3 colorMultiplier,
Vec3 blockCenter, Vec3 blockCenter,
@ -78,6 +80,7 @@ public class Faces {
switch (face) { switch (face) {
case TOP: case TOP:
return createRectangle( return createRectangle(
program,
texture, colorMultiplier, texture, colorMultiplier,
blockCenter.add(-0.5f, +0.5f, +0.5f), blockCenter.add(-0.5f, +0.5f, +0.5f),
new Vec3( 0, -1, 0), new Vec3( 0, -1, 0),
@ -85,6 +88,7 @@ public class Faces {
); );
case BOTTOM: case BOTTOM:
return createRectangle( return createRectangle(
program,
texture, colorMultiplier, texture, colorMultiplier,
blockCenter.add(-0.5f, -0.5f, -0.5f), blockCenter.add(-0.5f, -0.5f, -0.5f),
new Vec3( 0, +1, 0), new Vec3( 0, +1, 0),
@ -92,6 +96,7 @@ public class Faces {
); );
case NORTH: case NORTH:
return createRectangle( return createRectangle(
program,
texture, colorMultiplier, texture, colorMultiplier,
blockCenter.add(+0.5f, -0.5f, -0.5f), blockCenter.add(+0.5f, -0.5f, -0.5f),
new Vec3( 0, +1, 0), new Vec3( 0, +1, 0),
@ -99,6 +104,7 @@ public class Faces {
); );
case SOUTH: case SOUTH:
return createRectangle( return createRectangle(
program,
texture, colorMultiplier, texture, colorMultiplier,
blockCenter.add(-0.5f, +0.5f, -0.5f), blockCenter.add(-0.5f, +0.5f, -0.5f),
new Vec3( 0, -1, 0), new Vec3( 0, -1, 0),
@ -106,6 +112,7 @@ public class Faces {
); );
case EAST: case EAST:
return createRectangle( return createRectangle(
program,
texture, colorMultiplier, texture, colorMultiplier,
blockCenter.add(-0.5f, -0.5f, -0.5f), blockCenter.add(-0.5f, -0.5f, -0.5f),
new Vec3(+1, 0, 0), new Vec3(+1, 0, 0),
@ -113,6 +120,7 @@ public class Faces {
); );
case WEST: case WEST:
return createRectangle( return createRectangle(
program,
texture, colorMultiplier, texture, colorMultiplier,
blockCenter.add(+0.5f, +0.5f, -0.5f), blockCenter.add(+0.5f, +0.5f, -0.5f),
new Vec3(-1, 0, 0), new Vec3(-1, 0, 0),

View File

@ -18,7 +18,6 @@
package ru.windcorp.optica.client.graphics.model; package ru.windcorp.optica.client.graphics.model;
import glm.mat._4.Mat4; import glm.mat._4.Mat4;
import ru.windcorp.optica.client.graphics.world.WorldRenderer;
public abstract class Model implements WorldRenderable { public abstract class Model implements WorldRenderable {
@ -31,16 +30,16 @@ public abstract class Model implements WorldRenderable {
protected abstract Mat4 getTransform(int partIndex); protected abstract Mat4 getTransform(int partIndex);
@Override @Override
public void render(WorldRenderer renderer) { public void render(ShapeRenderHelper helper) {
for (int i = 0; i < parts.length; ++i) { for (int i = 0; i < parts.length; ++i) {
WorldRenderable part = parts[i]; WorldRenderable part = parts[i];
Mat4 transform = getTransform(i); Mat4 transform = getTransform(i);
try { try {
renderer.pushWorldTransform().mul(transform); helper.pushWorldTransform().mul(transform);
part.render(renderer); part.render(helper);
} finally { } finally {
renderer.popWorldTransform(); helper.popWorldTransform();
} }
} }
} }

View File

@ -24,7 +24,6 @@ import org.lwjgl.BufferUtils;
import ru.windcorp.optica.client.graphics.backend.Usage; import ru.windcorp.optica.client.graphics.backend.Usage;
import ru.windcorp.optica.client.graphics.backend.VertexBufferObject; import ru.windcorp.optica.client.graphics.backend.VertexBufferObject;
import ru.windcorp.optica.client.graphics.world.WorldRenderer;
public class Shape implements WorldRenderable { public class Shape implements WorldRenderable {
@ -48,18 +47,14 @@ public class Shape implements WorldRenderable {
this.usage = usage; this.usage = usage;
configureFaces(); configureFaces();
program.preprocess(this);
assembleBuffers(); assembleBuffers();
} }
public Shape(Usage usage, Face... faces) {
this(usage, ShapeRenderProgram.getDefault(), faces);
}
private void configureFaces() { private void configureFaces() {
for (Face face : faces) { for (Face face : faces) {
face.setShape(this); face.setShape(this);
face.computeNormals();
} }
} }
@ -153,12 +148,12 @@ public class Shape implements WorldRenderable {
} }
@Override @Override
public void render(WorldRenderer renderer) { public void render(ShapeRenderHelper helper) {
if (!initialized) initialize(); if (!initialized) initialize();
if (needsAssembly) assembleBuffers(); if (needsAssembly) assembleBuffers();
if (needsVBOUpdate) updateVBO(); if (needsVBOUpdate) updateVBO();
program.render(renderer, this); program.render(helper, this);
} }
private void initialize() { private void initialize() {

View File

@ -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 <https://www.gnu.org/licenses/>.
*******************************************************************************/
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<Mat4> 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();
}
}

View File

@ -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.VertexBufferObject.BindTarget;
import ru.windcorp.optica.client.graphics.backend.shaders.CombinedShader; 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.Program;
import ru.windcorp.optica.client.graphics.backend.shaders.attributes.AttributeVertexArray; import ru.windcorp.optica.client.graphics.backend.shaders.attributes.*;
import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.Uniform1Int; import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.*;
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.texture.Sprite; import ru.windcorp.optica.client.graphics.texture.Sprite;
import ru.windcorp.optica.client.graphics.world.WorldRenderer;
public class ShapeRenderProgram extends Program { 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 = private static final int DEFAULT_BYTES_PER_VERTEX =
3 * Float.BYTES + // Position 3 * Float.BYTES + // Position
3 * Float.BYTES + // Color multiplier 3 * Float.BYTES + // Color multiplier
2 * Float.BYTES + // Texture coordinates 2 * Float.BYTES; // Texture coordinates
3 * Float.BYTES; // Normals
private static final String SHAPE_VERTEX_SHADER_RESOURCE = private static final String SHAPE_VERTEX_SHADER_RESOURCE =
"Shape.vertex.glsl"; "Shape.vertex.glsl";
@ -67,24 +50,20 @@ public class ShapeRenderProgram extends Program {
private static final String private static final String
FINAL_TRANSFORM_UNIFORM_NAME = "finalTransform", FINAL_TRANSFORM_UNIFORM_NAME = "finalTransform",
WORLD_TRANSFORM_UNIFORM_NAME = "worldTransform",
POSITIONS_ATTRIBUTE_NAME = "inputPositions", POSITIONS_ATTRIBUTE_NAME = "inputPositions",
COLOR_MULTIPLER_ATTRIBUTE_NAME = "inputColorMultiplier", COLOR_MULTIPLER_ATTRIBUTE_NAME = "inputColorMultiplier",
TEXTURE_COORDS_ATTRIBUTE_NAME = "inputTextureCoords", TEXTURE_COORDS_ATTRIBUTE_NAME = "inputTextureCoords",
TEXTURE_SLOT_UNIFORM_NAME = "textureSlot", TEXTURE_SLOT_UNIFORM_NAME = "textureSlot",
TEXTURE_START_UNIFORM_NAME = "textureStart", TEXTURE_START_UNIFORM_NAME = "textureStart",
TEXTURE_SIZE_UNIFORM_NAME = "textureSize", TEXTURE_SIZE_UNIFORM_NAME = "textureSize";
NORMALS_ATTRIBUTE_NAME = "inputNormals";
private final Uniform4Matrix finalTransformUniform; private final Uniform4Matrix finalTransformUniform;
private final Uniform4Matrix worldTransformUniform;
private final AttributeVertexArray positionsAttribute; private final AttributeVertexArray positionsAttribute;
private final AttributeVertexArray colorsAttribute; private final AttributeVertexArray colorsAttribute;
private final AttributeVertexArray textureCoordsAttribute; private final AttributeVertexArray textureCoordsAttribute;
private final Uniform1Int textureSlotUniform; private final Uniform1Int textureSlotUniform;
private final Uniform2Float textureStartUniform; private final Uniform2Float textureStartUniform;
private final Uniform2Float textureSizeUniform; private final Uniform2Float textureSizeUniform;
private final AttributeVertexArray normalsAttribute;
public ShapeRenderProgram( public ShapeRenderProgram(
String[] vertexShaderResources, String[] vertexShaderResources,
@ -102,9 +81,6 @@ public class ShapeRenderProgram extends Program {
this.finalTransformUniform = getUniform(FINAL_TRANSFORM_UNIFORM_NAME) this.finalTransformUniform = getUniform(FINAL_TRANSFORM_UNIFORM_NAME)
.as4Matrix(); .as4Matrix();
this.worldTransformUniform = getUniform(WORLD_TRANSFORM_UNIFORM_NAME)
.as4Matrix();
this.positionsAttribute = this.positionsAttribute =
getAttribute(POSITIONS_ATTRIBUTE_NAME).asVertexArray(); getAttribute(POSITIONS_ATTRIBUTE_NAME).asVertexArray();
@ -122,9 +98,6 @@ public class ShapeRenderProgram extends Program {
this.textureSizeUniform = getUniform(TEXTURE_SIZE_UNIFORM_NAME) this.textureSizeUniform = getUniform(TEXTURE_SIZE_UNIFORM_NAME)
.as2Float(); .as2Float();
this.normalsAttribute = getAttribute(NORMALS_ATTRIBUTE_NAME)
.asVertexArray();
} }
private static String[] attachVertexShader(String[] others) { private static String[] attachVertexShader(String[] others) {
@ -136,36 +109,39 @@ public class ShapeRenderProgram extends Program {
} }
public void render( public void render(
WorldRenderer renderer, ShapeRenderHelper helper,
Shape shape Shape shape
) { ) {
use(); use();
configure(renderer); configure(helper);
bindVertices(shape.getVerticesVbo()); bindVertices(shape.getVerticesVbo());
bindIndices(shape.getIndicesVbo()); bindIndices(shape.getIndicesVbo());
try { try {
positionsAttribute.enable(); enableAttributes();
colorsAttribute.enable();
textureCoordsAttribute.enable();
normalsAttribute.enable();
for (Face face : shape.getFaces()) { for (Face face : shape.getFaces()) {
renderFace(face); renderFace(face);
} }
} finally { } finally {
disableAttributes();
}
}
protected void enableAttributes() {
positionsAttribute.enable();
colorsAttribute.enable();
textureCoordsAttribute.enable();
}
protected void disableAttributes() {
positionsAttribute.disable(); positionsAttribute.disable();
colorsAttribute.disable(); colorsAttribute.disable();
textureCoordsAttribute.disable(); textureCoordsAttribute.disable();
normalsAttribute.disable();
}
} }
protected void configure(WorldRenderer renderer) { protected void configure(ShapeRenderHelper helper) {
finalTransformUniform.set(renderer.getFinalTransform()); finalTransformUniform.set(helper.getFinalTransform());
worldTransformUniform.set(renderer.getWorldTransform());
} }
protected int bindVertices(VertexBufferObject vertices) { protected int bindVertices(VertexBufferObject vertices) {
@ -190,12 +166,6 @@ public class ShapeRenderProgram extends Program {
); );
offset += 2 * Float.BYTES; offset += 2 * Float.BYTES;
normalsAttribute.set(
3, GL11.GL_FLOAT, false, vertexStride, vertices,
offset
);
offset += 3 * Float.BYTES;
return offset; return offset;
} }
@ -224,7 +194,31 @@ public class ShapeRenderProgram extends Program {
return DEFAULT_BYTES_PER_VERTEX; 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 { private static class Vertex {
final Vec3 position; final Vec3 position;
@ -282,10 +276,7 @@ public class ShapeRenderProgram extends Program {
.putFloat(v.colorMultiplier.y) .putFloat(v.colorMultiplier.y)
.putFloat(v.colorMultiplier.z) .putFloat(v.colorMultiplier.z)
.putFloat(v.textureCoords.x) .putFloat(v.textureCoords.x)
.putFloat(v.textureCoords.y) .putFloat(v.textureCoords.y);
.putFloat(Float.NaN)
.putFloat(Float.NaN)
.putFloat(Float.NaN);
} }
result.flip(); result.flip();

View File

@ -24,6 +24,8 @@ import ru.windcorp.optica.client.graphics.texture.Texture;
public class Shapes { public class Shapes {
public static Shape createParallelepiped( // Try saying that 10 times fast public static Shape createParallelepiped( // Try saying that 10 times fast
ShapeRenderProgram program,
Vec3 origin, Vec3 origin,
Vec3 width, Vec3 width,
@ -44,6 +46,7 @@ public class Shapes {
Vec3 faceWidth = new Vec3(); Vec3 faceWidth = new Vec3();
Face top = Faces.createRectangle( Face top = Faces.createRectangle(
program,
topTexture, colorMultiplier, topTexture, colorMultiplier,
faceOrigin.set(origin).add(height).add(width), faceOrigin.set(origin).add(height).add(width),
faceWidth.set(width).negate(), faceWidth.set(width).negate(),
@ -51,6 +54,7 @@ public class Shapes {
); );
Face bottom = Faces.createRectangle( Face bottom = Faces.createRectangle(
program,
bottomTexture, colorMultiplier, bottomTexture, colorMultiplier,
origin, origin,
width, width,
@ -58,6 +62,7 @@ public class Shapes {
); );
Face north = Faces.createRectangle( Face north = Faces.createRectangle(
program,
northTexture, colorMultiplier, northTexture, colorMultiplier,
faceOrigin.set(origin).add(depth), faceOrigin.set(origin).add(depth),
width, width,
@ -65,6 +70,7 @@ public class Shapes {
); );
Face south = Faces.createRectangle( Face south = Faces.createRectangle(
program,
southTexture, colorMultiplier, southTexture, colorMultiplier,
faceOrigin.set(origin).add(width), faceOrigin.set(origin).add(width),
faceWidth.set(width).negate(), faceWidth.set(width).negate(),
@ -72,6 +78,7 @@ public class Shapes {
); );
Face east = Faces.createRectangle( Face east = Faces.createRectangle(
program,
eastTexture, colorMultiplier, eastTexture, colorMultiplier,
origin, origin,
depth, depth,
@ -79,6 +86,7 @@ public class Shapes {
); );
Face west = Faces.createRectangle( Face west = Faces.createRectangle(
program,
westTexture, colorMultiplier, westTexture, colorMultiplier,
faceOrigin.set(origin).add(width).add(depth), faceOrigin.set(origin).add(width).add(depth),
faceWidth.set(depth).negate(), faceWidth.set(depth).negate(),
@ -87,6 +95,7 @@ public class Shapes {
Shape result = new Shape( Shape result = new Shape(
Usage.STATIC, Usage.STATIC,
program,
top, bottom, north, south, east, west top, bottom, north, south, east, west
); );
@ -95,6 +104,8 @@ public class Shapes {
public static class PppBuilder { public static class PppBuilder {
private final ShapeRenderProgram program;
private final Vec3 origin = new Vec3(-0.5f, -0.5f, -0.5f); private final Vec3 origin = new Vec3(-0.5f, -0.5f, -0.5f);
private final Vec3 depth = new Vec3(1, 0, 0); private final Vec3 depth = new Vec3(1, 0, 0);
@ -111,6 +122,7 @@ public class Shapes {
private final Texture westTexture; private final Texture westTexture;
public PppBuilder( public PppBuilder(
ShapeRenderProgram program,
Texture top, Texture top,
Texture bottom, Texture bottom,
Texture north, Texture north,
@ -118,6 +130,7 @@ public class Shapes {
Texture east, Texture east,
Texture west Texture west
) { ) {
this.program = program;
this.topTexture = top; this.topTexture = top;
this.bottomTexture = bottom; this.bottomTexture = bottom;
this.northTexture = north; this.northTexture = north;
@ -126,8 +139,8 @@ public class Shapes {
this.westTexture = west; this.westTexture = west;
} }
public PppBuilder(Texture texture) { public PppBuilder(ShapeRenderProgram program, Texture texture) {
this(texture, texture, texture, texture, texture, texture); this(program, texture, texture, texture, texture, texture, texture);
} }
public PppBuilder setOrigin(Vec3 origin) { public PppBuilder setOrigin(Vec3 origin) {
@ -201,6 +214,7 @@ public class Shapes {
public Shape create() { public Shape create() {
return createParallelepiped( return createParallelepiped(
program,
origin, origin,
width, height, depth, width, height, depth,
colorMultiplier, colorMultiplier,

View File

@ -17,10 +17,8 @@
*******************************************************************************/ *******************************************************************************/
package ru.windcorp.optica.client.graphics.model; package ru.windcorp.optica.client.graphics.model;
import ru.windcorp.optica.client.graphics.world.WorldRenderer;
public interface WorldRenderable { public interface WorldRenderable {
void render(WorldRenderer renderer); void render(ShapeRenderHelper renderer);
} }

View File

@ -75,8 +75,6 @@ class Pixels {
data // Data buffer data // Data buffer
); );
glBindTexture(GL_TEXTURE_2D, 0);
return handle; return handle;
} }

View File

@ -42,18 +42,18 @@ public class Camera {
public Camera() {} public Camera() {}
public void apply(WorldRenderer renderer) { public void apply(WorldRenderHelper helper) {
Mat4 previous = renderer.getViewTransform(); Mat4 previous = helper.getViewTransform();
Glm.perspective( Glm.perspective(
computeFovY(), computeFovY(),
GraphicsInterface.getAspectRatio(), GraphicsInterface.getAspectRatio(),
0.01f, 10000.0f, 0.01f, 10000.0f,
renderer.pushViewTransform() helper.pushViewTransform()
).mul(previous); ).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(); position.negate();
} }

View File

@ -50,7 +50,10 @@ public class LayerWorld extends Layer {
private int movementY = 0; private int movementY = 0;
private int movementZ = 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()); private final WorldRender world = new WorldRender(new WorldData());
@ -66,13 +69,14 @@ public class LayerWorld extends Layer {
@Override @Override
protected void doRender() { protected void doRender() {
camera.apply(renderer); camera.apply(helper);
renderWorld(); renderWorld();
renderer.reset(); helper.reset();
angMat.set().rotateY(-camera.getYaw()); angMat.set().rotateY(-camera.getYaw());
tmp.set(movementX, 0, movementZ); tmp.set(movementX, 0, movementZ);
if (movementX != 0 && movementZ != 0) tmp.normalize();
angMat.mul_(tmp); // bug in jglm angMat.mul_(tmp); // bug in jglm
tmp.y = movementY; tmp.y = movementY;
tmp.mul(0.1f); tmp.mul(0.1f);
@ -82,10 +86,23 @@ public class LayerWorld extends Layer {
tmp.set(velocity); tmp.set(velocity);
tmp.mul((float) (GraphicsInterface.getFrameLength() * 60)); tmp.mul((float) (GraphicsInterface.getFrameLength() * 60));
camera.move(tmp); 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() { private void renderWorld() {
world.render(renderer); world.render(helper);
} }
public Camera getCamera() { public Camera getCamera() {

View File

@ -18,38 +18,20 @@
package ru.windcorp.optica.client.graphics.world; package ru.windcorp.optica.client.graphics.world;
import glm.mat._4.Mat4; import glm.mat._4.Mat4;
import ru.windcorp.optica.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.optica.common.util.StashingStack; import ru.windcorp.optica.common.util.StashingStack;
public class WorldRenderer { public class WorldRenderHelper extends ShapeRenderHelper {
private static final int TRANSFORM_STACK_SIZE = 64;
private final StashingStack<Mat4> worldTransformStack = new StashingStack<>(
TRANSFORM_STACK_SIZE, Mat4::new
);
private final StashingStack<Mat4> viewTransformStack = new StashingStack<>( private final StashingStack<Mat4> viewTransformStack = new StashingStack<>(
TRANSFORM_STACK_SIZE, Mat4::new TRANSFORM_STACK_SIZE, Mat4::new
); );
private final Mat4 finalTransform = new Mat4();
{ {
reset(); viewTransformStack.push().identity();
} }
public Mat4 pushWorldTransform() { private final Mat4 finalTransform = new Mat4();
Mat4 previous = worldTransformStack.getHead();
return worldTransformStack.push().set(previous);
}
public void popWorldTransform() {
worldTransformStack.removeHead();
}
public Mat4 getWorldTransform() {
return worldTransformStack.getHead();
}
public Mat4 pushViewTransform() { public Mat4 pushViewTransform() {
Mat4 previous = viewTransformStack.getHead(); Mat4 previous = viewTransformStack.getHead();
@ -64,13 +46,14 @@ public class WorldRenderer {
return viewTransformStack.getHead(); return viewTransformStack.getHead();
} }
@Override
public Mat4 getFinalTransform() { public Mat4 getFinalTransform() {
return finalTransform.set(getViewTransform()).mul(getWorldTransform()); return finalTransform.set(getViewTransform()).mul(getWorldTransform());
} }
@Override
public void reset() { public void reset() {
worldTransformStack.removeAll(); super.reset();
worldTransformStack.push().identity();
viewTransformStack.removeAll(); viewTransformStack.removeAll();
viewTransformStack.push().identity(); viewTransformStack.push().identity();
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*******************************************************************************/
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<Vertex> 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;
}
}
}

View File

@ -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;
import ru.windcorp.optica.client.graphics.model.StaticModel.Builder; import ru.windcorp.optica.client.graphics.model.StaticModel.Builder;
import ru.windcorp.optica.client.graphics.model.WorldRenderable; 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.BlockRender;
import ru.windcorp.optica.client.world.renders.BlockRenderNone; import ru.windcorp.optica.client.world.renders.BlockRenderNone;
import ru.windcorp.optica.client.world.renders.BlockRenders; 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()) { if (model == null || needsUpdate()) {
buildModel(); buildModel();
} }

View File

@ -21,7 +21,7 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.ChunkData;
import ru.windcorp.optica.common.world.WorldData; import ru.windcorp.optica.common.world.WorldData;
@ -55,7 +55,7 @@ public class WorldRender {
return chunks.values(); return chunks.values();
} }
public void render(WorldRenderer renderer) { public void render(ShapeRenderHelper renderer) {
renderer.pushWorldTransform().rotateX(-Math.PI / 2); renderer.pushWorldTransform().rotateX(-Math.PI / 2);
for (ChunkRender chunk : getChunks()) { for (ChunkRender chunk : getChunks()) {

View File

@ -18,7 +18,7 @@
package ru.windcorp.optica.client.world.renders; package ru.windcorp.optica.client.world.renders;
import ru.windcorp.optica.client.graphics.model.WorldRenderable; 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; import ru.windcorp.optica.common.util.Namespaced;
public abstract class BlockRender extends Namespaced { public abstract class BlockRender extends Namespaced {
@ -41,7 +41,7 @@ public abstract class BlockRender extends Namespaced {
this.optimizer = optimizer; this.optimizer = optimizer;
} }
public void render(WorldRenderer renderer) { public void render(ShapeRenderHelper renderer) {
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
"BlockRender.render() not implemented" "BlockRender.render() not implemented"
); );

View File

@ -24,6 +24,7 @@ import java.util.EnumMap;
import ru.windcorp.optica.client.graphics.model.Shapes; import ru.windcorp.optica.client.graphics.model.Shapes;
import ru.windcorp.optica.client.graphics.model.WorldRenderable; import ru.windcorp.optica.client.graphics.model.WorldRenderable;
import ru.windcorp.optica.client.graphics.texture.Texture; import ru.windcorp.optica.client.graphics.texture.Texture;
import ru.windcorp.optica.client.graphics.world.WorldRenderProgram;
import ru.windcorp.optica.common.block.BlockFace; import ru.windcorp.optica.common.block.BlockFace;
public abstract class BlockRenderTexturedCube extends BlockRender { public abstract class BlockRenderTexturedCube extends BlockRender {
@ -54,6 +55,7 @@ public abstract class BlockRenderTexturedCube extends BlockRender {
@Override @Override
public WorldRenderable createRenderable() { public WorldRenderable createRenderable() {
return new Shapes.PppBuilder( return new Shapes.PppBuilder(
WorldRenderProgram.getDefault(),
getTexture(TOP), getTexture(BOTTOM), getTexture(TOP), getTexture(BOTTOM),
getTexture(NORTH), getTexture(SOUTH), getTexture(NORTH), getTexture(SOUTH),
getTexture(EAST), getTexture(WEST) getTexture(EAST), getTexture(WEST)

View File

@ -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.Faces;
import ru.windcorp.optica.client.graphics.model.Shape; import ru.windcorp.optica.client.graphics.model.Shape;
import ru.windcorp.optica.client.graphics.texture.Texture; 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.ChunkRender;
import ru.windcorp.optica.client.world.renders.BlockRender; import ru.windcorp.optica.client.world.renders.BlockRender;
import ru.windcorp.optica.common.block.BlockFace; import ru.windcorp.optica.common.block.BlockFace;
@ -167,6 +168,7 @@ public class BlockRenderOpaqueCubeOptimizer extends BlockRenderOptimizer {
return new Shape( return new Shape(
Usage.STATIC, Usage.STATIC,
WorldRenderProgram.getDefault(),
shapeFaces.toArray(new Face[shapeFaces.size()]) shapeFaces.toArray(new Face[shapeFaces.size()])
); );
} }
@ -177,6 +179,7 @@ public class BlockRenderOpaqueCubeOptimizer extends BlockRenderOptimizer {
Texture texture = blockRender.getTexture(face); Texture texture = blockRender.getTexture(face);
return Faces.createBlockFace( return Faces.createBlockFace(
WorldRenderProgram.getDefault(),
texture, texture,
COLOR_MULTIPLIER, COLOR_MULTIPLIER,
blockCenter.set(x, y, z), blockCenter.set(x, y, z),

View File

@ -2,7 +2,6 @@
varying vec3 varyingColorMultiplier; varying vec3 varyingColorMultiplier;
varying vec2 varyingTextureCoords; varying vec2 varyingTextureCoords;
varying vec3 varyingNormals;
uniform sampler2D textureSlot; uniform sampler2D textureSlot;
uniform vec2 textureStart; uniform vec2 textureStart;
@ -36,16 +35,6 @@ void applyColorMultiplier() {
linearMultiply(gl_FragColor, vec4(varyingColorMultiplier, 1.0)); 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() { void applyAlpha() {
if (gl_FragColor.w < 0.01) { if (gl_FragColor.w < 0.01) {
discard; discard;

View File

@ -8,20 +8,13 @@ varying vec3 varyingColorMultiplier;
attribute vec2 inputTextureCoords; attribute vec2 inputTextureCoords;
varying vec2 varyingTextureCoords; varying vec2 varyingTextureCoords;
attribute vec3 inputNormals;
varying vec3 varyingNormals;
uniform mat4 worldTransform;
uniform mat4 finalTransform; uniform mat4 finalTransform;
vec4 applyFinalTransform(vec4 vector) { vec4 applyFinalTransform(vec4 vector) {
return finalTransform * vector; return finalTransform * vector;
} }
void transferToFragment() { void shapeTransferToFragment() {
varyingColorMultiplier = inputColorMultiplier; varyingColorMultiplier = inputColorMultiplier;
varyingTextureCoords = inputTextureCoords; varyingTextureCoords = inputTextureCoords;
mat3 worldRotation = mat3(worldTransform);
varyingNormals = normalize(worldRotation * inputNormals);
} }

View File

@ -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));
}

View File

@ -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);
}

View File

@ -2,5 +2,5 @@
void main(void) { void main(void) {
gl_Position = applyFinalTransform(vec4(inputPositions, 1.0)); gl_Position = applyFinalTransform(vec4(inputPositions, 1.0));
transferToFragment(); worldTransferToFragment();
} }