I wanted to play around a little bit with Java 13 text blocks and thought it would be a good idea to finally have GLSL shader source code literals embedded into Java source code to get a really single-source-file demo.
So here is a simple ray tracing example (static scene, static camera) in under 180 lines of code (Java + GLSL code) (requires JDK13+ with --enable-preview):
import org.lwjgl.glfw.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL.createCapabilities;
import static org.lwjgl.opengl.GL43C.*;
import static org.lwjgl.opengl.GLUtil.setupDebugMessageCallback;
import static org.lwjgl.system.MemoryUtil.*;
public class Main {
private static final int WINDOW_WIDTH = 800;
private static final int WINDOW_HEIGHT = 800;
private static final String QUAD_PROGRAM_VS_SOURCE = """
#version 430 core
out vec2 texcoord;
void main(void) {
vec2 vertex = vec2((gl_VertexID & 1) << 2, (gl_VertexID & 2) << 1) - vec2(1.0);
gl_Position = vec4(vertex, 0.0, 1.0);
texcoord = vertex * 0.5 + vec2(0.5, 0.5);
}
""";
private static final String QUAD_PROGRAM_FS_SOURCE = """
#version 430 core
uniform sampler2D tex;
in vec2 texcoord;
layout(location = 0) out vec4 color;
void main(void) {
color = texture2D(tex, texcoord);
}
""";
private static final String COMPUTE_SHADER_SOURCE = """
#version 430 core
layout(binding = 0, rgba8) uniform image2D framebufferImage;
layout(location = 0) uniform vec3 cam[5] = {
vec3(0.0, 2.0, 5.0), // <- position
vec3(-1.0, -1.0, -1.0), vec3(-1.0, 1.0, -1.0), // <- left corner directions
vec3(1.0, -1.0, -1.0), vec3(1.0, 1.0, -1.0) // <- right corner directions
};
struct box {
vec3 min, max;
};
#define NUM_BOXES 9
const box boxes[NUM_BOXES] = {
{vec3(-5.0, -0.1, -5.0), vec3(5.0, 0.0, 5.0)}, // <- bottom
{vec3(-5.1, 0.0, -5.0), vec3(-5.0, 5.0, 5.0)}, // <- left
{vec3(5.0, 0.0, -5.0), vec3(5.1, 5.0, 5.0)}, // <- right
{vec3(-5.0, 0.0, -5.1), vec3(5.0, 5.0, -5.0)}, // <- back
{vec3(-1.0, 1.0, -1.0), vec3(1.0, 1.1, 1.0)}, // <- table top
{vec3(-1.0, 0.0, -1.0), vec3(-0.8, 1.0, -0.8)}, // <- table foot
{vec3(-1.0, 0.0, 0.8), vec3(-0.8, 1.0, 1.0)}, // <- table foot
{vec3(0.8, 0.0, -1.0), vec3(1.0, 1.0, -0.8)}, // <- table foot
{vec3(0.8, 0.0, 0.8), vec3(1.0, 1.0, 1.0)} // <- table foot
};
struct hitinfo {
float near;
int i;
};
vec2 intersectBox(vec3 origin, vec3 invdir, box b) {
vec3 tMin = (b.min - origin) * invdir;
vec3 tMax = (b.max - origin) * invdir;
vec3 t1 = min(tMin, tMax);
vec3 t2 = max(tMin, tMax);
float tNear = max(max(t1.x, t1.y), t1.z);
float tFar = min(min(t2.x, t2.y), t2.z);
return vec2(tNear, tFar);
}
bool intersectBoxes(vec3 origin, vec3 invdir, out hitinfo info) {
float smallest = 1.0/0.0;
bool found = false;
for (int i = 0; i < NUM_BOXES; i++) {
vec2 lambda = intersectBox(origin, invdir, boxes[i]);
if (lambda.y >= 0.0 && lambda.x < lambda.y && lambda.x < smallest) {
info.near = lambda.x;
info.i = i;
smallest = lambda.x;
found = true;
}
}
return found;
}
vec3 trace(vec3 origin, vec3 dir) {
hitinfo hinfo;
if (!intersectBoxes(origin, 1.0/dir, hinfo))
return vec3(0.0); // <- nothing hit, return black
box b = boxes[hinfo.i];
return vec3(float(hinfo.i+1) / NUM_BOXES);
}
layout(local_size_x = 8, local_size_y = 8) in;
void main(void) {
ivec2 px = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(framebufferImage);
if (any(greaterThanEqual(px, size)))
return;
vec2 p = (vec2(px) + vec2(0.5)) / vec2(size);
vec3 dir = mix(mix(cam[1], cam[2], p.y), mix(cam[3], cam[4], p.y), p.x);
imageStore(framebufferImage, px, vec4(trace(cam[0], normalize(dir)), 1.0));
}
""";
public static void main(String[] args) {
// Initialize GLFW and create window
if (!glfwInit())
throw new IllegalStateException("Unable to initialize GLFW");
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
long window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Ray Tracing Tutorial 1", NULL, NULL);
if (window == NULL)
throw new AssertionError("Failed to create the GLFW window");
glfwSetKeyCallback(window, new GLFWKeyCallback() {
public void invoke(long window, int key, int scancode, int action, int mods) {
if (action == GLFW_RELEASE && key == GLFW_KEY_ESCAPE)
glfwSetWindowShouldClose(window, true);
}
});
// Make context current and install debug message callback
glfwMakeContextCurrent(window);
createCapabilities();
setupDebugMessageCallback();
// Create VAO
glBindVertexArray(glGenVertexArrays());
// Create framebuffer texture to render into
int framebuffer = glGenTextures();
glBindTexture(GL_TEXTURE_2D, framebuffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, WINDOW_WIDTH, WINDOW_HEIGHT);
glBindImageTexture(0, framebuffer, 0, false, 0, GL_WRITE_ONLY, GL_RGBA8);
// Create program to render framebuffer texture as fullscreen quad
int quadProgram = glCreateProgram();
int quadProgramVs = glCreateShader(GL_VERTEX_SHADER);
int quadProgramFs = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(quadProgramVs, QUAD_PROGRAM_VS_SOURCE);
glShaderSource(quadProgramFs, QUAD_PROGRAM_FS_SOURCE);
glCompileShader(quadProgramVs);
glCompileShader(quadProgramFs);
glAttachShader(quadProgram, quadProgramVs);
glAttachShader(quadProgram, quadProgramFs);
glLinkProgram(quadProgram);
// Create ray tracing compute shader
int computeProgram = glCreateProgram();
int computeProgramShader = glCreateShader(GL_COMPUTE_SHADER);
glShaderSource(computeProgramShader, COMPUTE_SHADER_SOURCE);
glCompileShader(computeProgramShader);
glAttachShader(computeProgram, computeProgramShader);
glLinkProgram(computeProgram);
// Determine number of work groups to dispatch
int numGroupsX = (int) Math.ceil((double)WINDOW_WIDTH / 8);
int numGroupsY = (int) Math.ceil((double)WINDOW_HEIGHT / 8);
// Make window visible and loop until window should be closed
glfwShowWindow(window);
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
// Trace the scene
glUseProgram(computeProgram);
glDispatchCompute(numGroupsX, numGroupsY, 1);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
// Display framebuffer texture
glUseProgram(quadProgram);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
}
}
}