Add computeStyle demos (#354)
This commit is contained in:
97
openrndr-demos/src/demo/kotlin/DemoComputeStyle01.kt
Normal file
97
openrndr-demos/src/demo/kotlin/DemoComputeStyle01.kt
Normal file
@@ -0,0 +1,97 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.math.Vector3
|
||||
|
||||
/**
|
||||
* SSBO -> https://www.khronos.org/opengl/wiki/Shader_Storage_Buffer_Object
|
||||
*
|
||||
* This program demonstrates
|
||||
* - how to create an SSBO
|
||||
* - how to populate an SSBO with data
|
||||
* - how to pass an SSBO to a compute shader
|
||||
* - how to download the SSBO data to the CPU before and after executing the compute shader
|
||||
*
|
||||
* It prints the before/after data to observe how it was modified by the compute shader.
|
||||
*
|
||||
* Writing a useful compute shader that processes data faster than in the CPU is NOT a goal
|
||||
* of this program, since such a simple calculation would be faster and easier if done completely in the CPU.
|
||||
*
|
||||
* Notice how the execute() call has a width, height and depth of 1
|
||||
* (basically doing just one computation).
|
||||
*
|
||||
* A useful compute shader would do a large number of parallel computations.
|
||||
* This will be presented in a different demo.
|
||||
*
|
||||
* Output: byteBuffer -> text
|
||||
*/
|
||||
|
||||
fun main() = application {
|
||||
program {
|
||||
// Define SSBO format
|
||||
val fmt = shaderStorageFormat {
|
||||
primitive("time", BufferPrimitiveType.FLOAT32)
|
||||
primitive("vertex", BufferPrimitiveType.VECTOR2_FLOAT32, 3)
|
||||
struct("Particle", "particles", 5) {
|
||||
primitive("pos", BufferPrimitiveType.VECTOR3_FLOAT32)
|
||||
primitive("age", BufferPrimitiveType.FLOAT32)
|
||||
}
|
||||
}
|
||||
println("Study the padding in the format:\n$fmt\n")
|
||||
|
||||
// Create SSBO
|
||||
val ssbo = shaderStorageBuffer(fmt)
|
||||
|
||||
// Populate SSBO
|
||||
ssbo.put {
|
||||
write(3.0.toFloat()) // time
|
||||
repeat(3) {
|
||||
write(Vector2(1.1, 1.2)) // vertex
|
||||
}
|
||||
repeat(5) {
|
||||
write(Vector3(2.1, 2.2, 2.3)) // pos
|
||||
write(1.0.toFloat()) // age
|
||||
}
|
||||
}
|
||||
|
||||
// Create Compute Shader
|
||||
val cs = computeStyle {
|
||||
computeTransform = """
|
||||
b_myData.time = 3.3;
|
||||
b_myData.vertex[0] = vec2(7.01);
|
||||
b_myData.vertex[1] = vec2(7.02);
|
||||
b_myData.vertex[2] = vec2(7.03);
|
||||
b_myData.particles[0].pos = vec3(112.0);
|
||||
b_myData.particles[0].age = 111.0;
|
||||
""".trimIndent()
|
||||
}
|
||||
cs.buffer("myData", ssbo)
|
||||
|
||||
// Download SSBO data to CPU
|
||||
val byteBufferBeforeExecute = ssbo.createByteBuffer()
|
||||
byteBufferBeforeExecute.rewind()
|
||||
ssbo.read(byteBufferBeforeExecute)
|
||||
|
||||
// Execute compute shader
|
||||
cs.execute(1, 1, 1)
|
||||
|
||||
// Download SSBO data to CPU
|
||||
val byteBufferAfterExecute = ssbo.createByteBuffer()
|
||||
byteBufferAfterExecute.rewind()
|
||||
ssbo.read(byteBufferAfterExecute)
|
||||
|
||||
// Debugging
|
||||
|
||||
// Notice the (maybe unexpected) 0.0 padding values printed on the console.
|
||||
// Depending on the variable size in bytes, padding may be added by the system
|
||||
// to align them in memory. This will depend on the sizes of the involved variables,
|
||||
// and their order. For instance, a vec3 and a float do not require padding, but
|
||||
// a float followed by a vec3 pads the float with 3 values, and the vec3 with one.
|
||||
// Run compute02.kt and study the output to observe a more inefficient layout.
|
||||
byteBufferBeforeExecute.rewind()
|
||||
byteBufferAfterExecute.rewind()
|
||||
repeat(ssbo.format.size / 4) {
|
||||
println("$it: ${byteBufferBeforeExecute.float} -> ${byteBufferAfterExecute.float}")
|
||||
}
|
||||
}
|
||||
}
|
||||
83
openrndr-demos/src/demo/kotlin/DemoComputeStyle02.kt
Normal file
83
openrndr-demos/src/demo/kotlin/DemoComputeStyle02.kt
Normal file
@@ -0,0 +1,83 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.math.Vector3
|
||||
|
||||
/**
|
||||
* A program identical to compute01.kt, except that the order of variables
|
||||
* `age` and `pos` have been swapped, resulting in a less ideal memory layout.
|
||||
* In this case the SSBO requires 192 bytes instead of 112, and padding is
|
||||
* inserted after the variables `time`, `age` and `pos`.
|
||||
*
|
||||
* Output: byteBuffer -> text
|
||||
*/
|
||||
|
||||
fun main() = application {
|
||||
program {
|
||||
// Define SSBO format
|
||||
val fmt = shaderStorageFormat {
|
||||
primitive("time", BufferPrimitiveType.FLOAT32)
|
||||
primitive("vertex", BufferPrimitiveType.VECTOR2_FLOAT32, 3)
|
||||
struct("Particle", "particles", 5) {
|
||||
primitive("age", BufferPrimitiveType.FLOAT32)
|
||||
primitive("pos", BufferPrimitiveType.VECTOR3_FLOAT32)
|
||||
}
|
||||
}
|
||||
println("Study the padding in the format:\n$fmt\n")
|
||||
|
||||
// Create SSBO
|
||||
val ssbo = shaderStorageBuffer(fmt)
|
||||
|
||||
// Populate SSBO
|
||||
ssbo.put {
|
||||
write(3.0.toFloat()) // time
|
||||
repeat(3) {
|
||||
write(Vector2(1.1, 1.2)) // vertex
|
||||
}
|
||||
repeat(5) {
|
||||
write(1.0.toFloat()) // age
|
||||
write(Vector3(2.1, 2.2, 2.3))// pos
|
||||
}
|
||||
}
|
||||
|
||||
// Create Compute Shader
|
||||
val cs = computeStyle {
|
||||
computeTransform = """
|
||||
b_myData.time = 3.3;
|
||||
b_myData.vertex[0] = vec2(7.01);
|
||||
b_myData.vertex[1] = vec2(7.02);
|
||||
b_myData.vertex[2] = vec2(7.03);
|
||||
b_myData.particles[0].pos = vec3(112.0);
|
||||
b_myData.particles[0].age = 111.0;
|
||||
""".trimIndent()
|
||||
}
|
||||
cs.buffer("myData", ssbo)
|
||||
|
||||
// Download SSBO data to CPU
|
||||
val byteBufferBeforeExecute = ssbo.createByteBuffer()
|
||||
byteBufferBeforeExecute.rewind()
|
||||
ssbo.read(byteBufferBeforeExecute)
|
||||
|
||||
// Execute compute shader
|
||||
cs.execute(1, 1, 1)
|
||||
|
||||
// Download SSBO data to CPU
|
||||
val byteBufferAfterExecute = ssbo.createByteBuffer()
|
||||
byteBufferAfterExecute.rewind()
|
||||
ssbo.read(byteBufferAfterExecute)
|
||||
|
||||
// Debugging
|
||||
|
||||
// Notice the (maybe unexpected) 0.0 padding values printed on the console.
|
||||
// Depending on the variable size in bytes, padding may be added by the system
|
||||
// to align them in memory. This will depend on the sizes of the involved variables,
|
||||
// and their order. For instance, a vec3 and a float do not require padding, but
|
||||
// a float followed by a vec3 pads the float with 3 values, and the vec3 with one.
|
||||
// Run compute02.kt and study the output to observe a more inefficient layout.
|
||||
byteBufferBeforeExecute.rewind()
|
||||
byteBufferAfterExecute.rewind()
|
||||
repeat(ssbo.format.size / 4) {
|
||||
println("$it: ${byteBufferBeforeExecute.float} -> ${byteBufferAfterExecute.float}")
|
||||
}
|
||||
}
|
||||
}
|
||||
88
openrndr-demos/src/demo/kotlin/DemoComputeStyle03.kt
Normal file
88
openrndr-demos/src/demo/kotlin/DemoComputeStyle03.kt
Normal file
@@ -0,0 +1,88 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.math.IntVector3
|
||||
|
||||
/**
|
||||
* This program demonstrates
|
||||
* - how to use a compute shader and an SSBO to do many computations in parallel
|
||||
* - how to use a compute shader to initialize an SSBO
|
||||
* - how to use a different shader to update the SSBO
|
||||
*
|
||||
* Note the `workGroupSize` property. The GPU splits tasks
|
||||
* into chunks and computes those in parallel. The ideal workGroupSize depends on
|
||||
* the GPU being used. Too small of a size may be inefficient.
|
||||
*
|
||||
* In some cases a compute shader works with 2D images or 3D data structures, but in this
|
||||
* program we are processing the elements of a 1D array. That's why we only
|
||||
* increase the x value to 32, leaving y and z equal to 1.
|
||||
*
|
||||
* Note: this program only does the computation, but does not visualize the results
|
||||
* in any way. We will do that in another program.
|
||||
*
|
||||
* Output: none
|
||||
*/
|
||||
|
||||
fun main() = application {
|
||||
program {
|
||||
val particleCount = 4800
|
||||
// Define SSBO format
|
||||
val fmt = shaderStorageFormat {
|
||||
struct("Particle", "particle", particleCount) {
|
||||
primitive("pos", BufferPrimitiveType.VECTOR2_FLOAT32)
|
||||
primitive("velocity", BufferPrimitiveType.VECTOR2_FLOAT32)
|
||||
}
|
||||
}
|
||||
println("Study the padding in the format:\n$fmt\n")
|
||||
|
||||
// Create SSBO
|
||||
val particlesSSBO = shaderStorageBuffer(fmt)
|
||||
|
||||
// Create Compute Shaders
|
||||
val initCS = computeStyle {
|
||||
computeTransform = """
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
b_particles.particle[id].pos = vec2(320.0, 240.0);
|
||||
b_particles.particle[id].velocity = vec2(cos(id), sin(id));
|
||||
""".trimIndent()
|
||||
workGroupSize = IntVector3(32, 1, 1)
|
||||
}
|
||||
val updateCS = computeStyle {
|
||||
computeTransform = """
|
||||
// The id of the element being currently processed
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
|
||||
// Add velocity to position
|
||||
b_particles.particle[id].pos += b_particles.particle[id].velocity;
|
||||
|
||||
// Deal with the particle trying to escape the window
|
||||
if(b_particles.particle[id].pos.x < 0.0) {
|
||||
b_particles.particle[id].pos.x = 0.0;
|
||||
b_particles.particle[id].velocity.x = abs(b_particles.particle[id].velocity.x);
|
||||
}
|
||||
if(b_particles.particle[id].pos.y < 0.0) {
|
||||
b_particles.particle[id].pos.y = 0.0;
|
||||
b_particles.particle[id].velocity.y = abs(b_particles.particle[id].velocity.y);
|
||||
}
|
||||
if(b_particles.particle[id].pos.x > p_windowSize.x) {
|
||||
b_particles.particle[id].pos.x = p_windowSize.x;
|
||||
b_particles.particle[id].velocity.x = -abs(b_particles.particle[id].velocity.x);
|
||||
}
|
||||
if(b_particles.particle[id].pos.y > p_windowSize.y) {
|
||||
b_particles.particle[id].pos.y = p_windowSize.y;
|
||||
b_particles.particle[id].velocity.y = -abs(b_particles.particle[id].velocity.y);
|
||||
}
|
||||
""".trimIndent()
|
||||
workGroupSize = IntVector3(32, 1, 1)
|
||||
}
|
||||
|
||||
// Execute initCS
|
||||
initCS.buffer("particles", particlesSSBO)
|
||||
initCS.execute(particleCount / initCS.workGroupSize.x)
|
||||
|
||||
extend {
|
||||
updateCS.buffer("particles", particlesSSBO)
|
||||
updateCS.parameter("windowSize", drawer.bounds.dimensions)
|
||||
updateCS.execute(particleCount / updateCS.workGroupSize.x)
|
||||
}
|
||||
}
|
||||
}
|
||||
107
openrndr-demos/src/demo/kotlin/DemoComputeStyle04.kt
Normal file
107
openrndr-demos/src/demo/kotlin/DemoComputeStyle04.kt
Normal file
@@ -0,0 +1,107 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.math.IntVector3
|
||||
|
||||
/**
|
||||
* This program draws moving points to demonstrate
|
||||
* how to write the resulting calculations of a compute shader into a vertex buffer.
|
||||
*
|
||||
* There are various ways to output the calculations and make them visible to the user:
|
||||
* - Write into a vertex buffer, which can be rendered as points, lines or triangles by OPENRNDR
|
||||
* (this program).
|
||||
* - Update a colorBuffer (the pixels of a texture).
|
||||
*
|
||||
* Output: vertexBuffer -> POINTS
|
||||
*/
|
||||
|
||||
fun main() = application {
|
||||
program {
|
||||
val particleCount = 4800
|
||||
// Define SSBO format
|
||||
val fmt = shaderStorageFormat {
|
||||
struct("Particle", "particle", particleCount) {
|
||||
primitive("pos", BufferPrimitiveType.VECTOR2_FLOAT32)
|
||||
primitive("velocity", BufferPrimitiveType.VECTOR2_FLOAT32)
|
||||
}
|
||||
}
|
||||
println("Study the padding in the format:\n$fmt\n")
|
||||
|
||||
// Create SSBO
|
||||
val particleSSBO = shaderStorageBuffer(fmt)
|
||||
|
||||
// Create a vertex buffer.
|
||||
// Padding is required if position has less than 4 dimensions.
|
||||
// val vb = vertexBuffer(vertexFormat {
|
||||
// position(3)
|
||||
// paddingFloat(1)
|
||||
// }, particleCount)
|
||||
|
||||
// With BufferAlignment.STD430 padding is taken care of
|
||||
val vb = vertexBuffer(vertexFormat(BufferAlignment.STD430) {
|
||||
position(3)
|
||||
}, particleCount)
|
||||
|
||||
// Create Compute Shaders
|
||||
val initCS = computeStyle {
|
||||
computeTransform = """
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
b_particles.particle[id].pos = vec2(320.0, 240.0);
|
||||
b_particles.particle[id].velocity = vec2(cos(id), sin(id));
|
||||
""".trimIndent()
|
||||
workGroupSize = IntVector3(32, 1, 1)
|
||||
}
|
||||
val updateCS = computeStyle {
|
||||
// We can create GLSL functions in the computePreamble.
|
||||
// Thanks to an `inout` variable, we can shorten the code.
|
||||
// (Compare this with compute03.kt)
|
||||
computePreamble = """
|
||||
void updateParticle(inout Particle p) {
|
||||
// Add velocity to position
|
||||
p.pos += p.velocity;
|
||||
|
||||
// Deal with the particle trying to escape the window
|
||||
if(p.pos.x < 0.0) {
|
||||
p.pos.x = 0.0;
|
||||
p.velocity.x = abs(p.velocity.x);
|
||||
}
|
||||
if(p.pos.y < 0.0) {
|
||||
p.pos.y = 0.0;
|
||||
p.velocity.y = abs(p.velocity.y);
|
||||
}
|
||||
if(p.pos.x > p_windowSize.x) {
|
||||
p.pos.x = p_windowSize.x;
|
||||
p.velocity.x = -abs(p.velocity.x);
|
||||
}
|
||||
if(p.pos.y > p_windowSize.y) {
|
||||
p.pos.y = p_windowSize.y;
|
||||
p.velocity.y = -abs(p.velocity.y);
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
computeTransform = """
|
||||
// The id of the element being currently processed
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
updateParticle(b_particles.particle[id]);
|
||||
|
||||
// Update the vertexBuffer with data from the shaderStorageBuffer
|
||||
b_vb.vertex[id].position.xy = b_particles.particle[id].pos;
|
||||
""".trimIndent()
|
||||
workGroupSize = IntVector3(32, 1, 1)
|
||||
}
|
||||
|
||||
// Execute initCS
|
||||
initCS.buffer("particles", particleSSBO)
|
||||
initCS.execute(particleCount / initCS.workGroupSize.x)
|
||||
|
||||
extend {
|
||||
updateCS.buffer("vb", vb.shaderStorageBufferView())
|
||||
updateCS.buffer("particles", particleSSBO)
|
||||
updateCS.parameter("windowSize", drawer.bounds.dimensions)
|
||||
updateCS.execute(particleCount /updateCS.workGroupSize.x)
|
||||
|
||||
drawer.fill = ColorRGBa.WHITE
|
||||
drawer.vertexBuffer(vb, DrawPrimitive.POINTS)
|
||||
}
|
||||
}
|
||||
}
|
||||
97
openrndr-demos/src/demo/kotlin/DemoComputeStyle05.kt
Normal file
97
openrndr-demos/src/demo/kotlin/DemoComputeStyle05.kt
Normal file
@@ -0,0 +1,97 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.math.IntVector3
|
||||
|
||||
/**
|
||||
* This program demonstrates how to change the rendering type from POINTS to LINE_LOOP.
|
||||
* In everything else, it's identical to compute04.kt
|
||||
*
|
||||
* Output: vertexBuffer -> LINE_LOOP
|
||||
*/
|
||||
|
||||
fun main() = application {
|
||||
program {
|
||||
val particleCount = 4800
|
||||
// Define SSBO format
|
||||
val fmt = shaderStorageFormat {
|
||||
struct("Particle", "particle", particleCount) {
|
||||
primitive("pos", BufferPrimitiveType.VECTOR2_FLOAT32)
|
||||
primitive("velocity", BufferPrimitiveType.VECTOR2_FLOAT32)
|
||||
}
|
||||
}
|
||||
println("Study the padding in the format:\n$fmt\n")
|
||||
|
||||
// Create SSBO
|
||||
val particleSSBO = shaderStorageBuffer(fmt)
|
||||
|
||||
// Create a vertex buffer.
|
||||
// Padding is required if position has less than 4 dimensions.
|
||||
// val vb = vertexBuffer(vertexFormat {
|
||||
// position(3)
|
||||
// paddingFloat(1)
|
||||
// }, particleCount)
|
||||
|
||||
// With BufferAlignment.STD430 padding is taken care of
|
||||
val vb = vertexBuffer(vertexFormat(BufferAlignment.STD430) {
|
||||
position(3)
|
||||
}, particleCount)
|
||||
|
||||
// Create Compute Shaders
|
||||
val initCS = computeStyle {
|
||||
computeTransform = """
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
b_particles.particle[id].pos = vec2(320.0, 240.0);
|
||||
b_particles.particle[id].velocity = vec2(cos(id), sin(id));
|
||||
""".trimIndent()
|
||||
workGroupSize = IntVector3(32, 1, 1)
|
||||
}
|
||||
val updateCS = computeStyle {
|
||||
computePreamble = """
|
||||
void updateParticle(inout Particle p) {
|
||||
// Add velocity to position
|
||||
p.pos += p.velocity;
|
||||
|
||||
// Deal with the particle trying to escape the window
|
||||
if(p.pos.x < 0.0) {
|
||||
p.pos.x = 0.0;
|
||||
p.velocity.x = abs(p.velocity.x);
|
||||
}
|
||||
if(p.pos.y < 0.0) {
|
||||
p.pos.y = 0.0;
|
||||
p.velocity.y = abs(p.velocity.y);
|
||||
}
|
||||
if(p.pos.x > p_windowSize.x) {
|
||||
p.pos.x = p_windowSize.x;
|
||||
p.velocity.x = -abs(p.velocity.x);
|
||||
}
|
||||
if(p.pos.y > p_windowSize.y) {
|
||||
p.pos.y = p_windowSize.y;
|
||||
p.velocity.y = -abs(p.velocity.y);
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
computeTransform = """
|
||||
// The id of the element being currently processed
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
updateParticle(b_particles.particle[id]);
|
||||
b_vb.vertex[id].position.xy = b_particles.particle[id].pos;
|
||||
""".trimIndent()
|
||||
workGroupSize = IntVector3(32, 1, 1)
|
||||
}
|
||||
|
||||
// Execute initCS
|
||||
initCS.buffer("particles", particleSSBO)
|
||||
initCS.execute(particleCount / initCS.workGroupSize.x)
|
||||
|
||||
extend {
|
||||
updateCS.buffer("vb", vb.shaderStorageBufferView())
|
||||
updateCS.buffer("particles", particleSSBO)
|
||||
updateCS.parameter("windowSize", drawer.bounds.dimensions)
|
||||
updateCS.execute(particleCount / updateCS.workGroupSize.x)
|
||||
|
||||
drawer.fill = ColorRGBa.WHITE
|
||||
drawer.vertexBuffer(vb, DrawPrimitive.LINE_LOOP)
|
||||
}
|
||||
}
|
||||
}
|
||||
93
openrndr-demos/src/demo/kotlin/DemoComputeStyle06.kt
Normal file
93
openrndr-demos/src/demo/kotlin/DemoComputeStyle06.kt
Normal file
@@ -0,0 +1,93 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.math.IntVector3
|
||||
|
||||
/**
|
||||
* This program is almost identical to compute04.kt, but instead of writing into
|
||||
* a vertex buffer, it draws the particles into a 2D image (a ColorBuffer).
|
||||
*
|
||||
* Output: 2D Image
|
||||
*/
|
||||
|
||||
fun main() = application {
|
||||
program {
|
||||
val particleCount = 4800
|
||||
// Define SSBO format
|
||||
val fmt = shaderStorageFormat {
|
||||
struct("Particle", "particle", particleCount) {
|
||||
primitive("pos", BufferPrimitiveType.VECTOR2_FLOAT32)
|
||||
primitive("velocity", BufferPrimitiveType.VECTOR2_FLOAT32)
|
||||
}
|
||||
}
|
||||
println("Study the padding in the format:\n$fmt\n")
|
||||
|
||||
// Create SSBO
|
||||
val particleSSBO = shaderStorageBuffer(fmt)
|
||||
|
||||
// Create a color buffer to write into.
|
||||
val cb = colorBuffer(width, height, type = ColorType.FLOAT32)
|
||||
|
||||
// Create Compute Shaders
|
||||
val initCS = computeStyle {
|
||||
computeTransform = """
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
b_particles.particle[id].pos = vec2(320.0, 240.0);
|
||||
b_particles.particle[id].velocity = vec2(cos(id), sin(id));
|
||||
""".trimIndent()
|
||||
workGroupSize = IntVector3(32, 1, 1)
|
||||
}
|
||||
val updateCS = computeStyle {
|
||||
computePreamble = """
|
||||
void updateParticle(inout Particle p) {
|
||||
// Add velocity to position
|
||||
p.pos += p.velocity;
|
||||
|
||||
// Deal with the particle trying to escape the window
|
||||
if(p.pos.x < 0.0) {
|
||||
p.pos.x = 0.0;
|
||||
p.velocity.x = abs(p.velocity.x);
|
||||
}
|
||||
if(p.pos.y < 0.0) {
|
||||
p.pos.y = 0.0;
|
||||
p.velocity.y = abs(p.velocity.y);
|
||||
}
|
||||
if(p.pos.x > p_windowSize.x) {
|
||||
p.pos.x = p_windowSize.x;
|
||||
p.velocity.x = -abs(p.velocity.x);
|
||||
}
|
||||
if(p.pos.y > p_windowSize.y) {
|
||||
p.pos.y = p_windowSize.y;
|
||||
p.velocity.y = -abs(p.velocity.y);
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
computeTransform = """
|
||||
// The id of the element being currently processed
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
updateParticle(b_particles.particle[id]);
|
||||
|
||||
// write into the image
|
||||
imageStore(p_img, ivec2(b_particles.particle[id].pos.xy), vec4(1.0));
|
||||
""".trimIndent()
|
||||
workGroupSize = IntVector3(32, 1, 1)
|
||||
}
|
||||
// Execute initCS
|
||||
initCS.buffer("particles", particleSSBO)
|
||||
initCS.execute(particleCount, initCS.workGroupSize.x)
|
||||
|
||||
extend {
|
||||
// Clear the image, otherwise all pixels become eventually white
|
||||
cb.fill(ColorRGBa.TRANSPARENT)
|
||||
|
||||
// Pass image to the compute shader.
|
||||
// We can choose between READ, READ_WRITE or WRITE.
|
||||
updateCS.image("img", cb.imageBinding(0, ImageAccess.WRITE))
|
||||
updateCS.buffer("particles", particleSSBO)
|
||||
updateCS.parameter("windowSize", drawer.bounds.dimensions)
|
||||
updateCS.execute(particleCount / updateCS.workGroupSize.x)
|
||||
|
||||
drawer.image(cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
120
openrndr-demos/src/demo/kotlin/DemoComputeStyle07.kt
Normal file
120
openrndr-demos/src/demo/kotlin/DemoComputeStyle07.kt
Normal file
@@ -0,0 +1,120 @@
|
||||
package compute
|
||||
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.math.IntVector3
|
||||
|
||||
/**
|
||||
* This program demonstrates
|
||||
* - how to modify the particles, adding color, age and ageVelocity.
|
||||
* - make the particles wrap around the edges instead of bouncing on them.
|
||||
*
|
||||
* Output: 2D Image
|
||||
*/
|
||||
|
||||
fun main() = application {
|
||||
program {
|
||||
val particleCount = 4800
|
||||
// Define SSBO format
|
||||
val fmt = shaderStorageFormat {
|
||||
struct("Particle", "particle", particleCount) {
|
||||
primitive("pos", BufferPrimitiveType.VECTOR2_FLOAT32)
|
||||
primitive("velocity", BufferPrimitiveType.VECTOR2_FLOAT32)
|
||||
primitive("color", BufferPrimitiveType.VECTOR3_FLOAT32)
|
||||
primitive("age", BufferPrimitiveType.FLOAT32)
|
||||
primitive("ageVelocity", BufferPrimitiveType.FLOAT32)
|
||||
}
|
||||
}
|
||||
println("Study the padding in the format:\n$fmt\n")
|
||||
|
||||
// Create SSBO
|
||||
val particleSSBO = shaderStorageBuffer(fmt)
|
||||
|
||||
// Create a color buffer to write into.
|
||||
val cb = colorBuffer(width, height, type = ColorType.FLOAT32)
|
||||
|
||||
// Create Compute Shaders
|
||||
val initCS = computeStyle {
|
||||
computePreamble = """
|
||||
// From lygia.xyz
|
||||
vec3 hue2rgb(const in float hue) {
|
||||
float R = abs(hue * 6.0 - 3.0) - 1.0;
|
||||
float G = 2.0 - abs(hue * 6.0 - 2.0);
|
||||
float B = 2.0 - abs(hue * 6.0 - 4.0);
|
||||
return clamp(vec3(R,G,B), 0.0, 1.0);
|
||||
}
|
||||
void initParticle(uint id, inout Particle p) {
|
||||
float k = 100.0 / $particleCount;
|
||||
p.velocity = vec2(cos(id * k), sin(id * k));
|
||||
p.pos = vec2(320.0, 240.0) + p.velocity * id * 0.0003;
|
||||
p.color = hue2rgb(fract(id * k / 4.0));
|
||||
p.age = id * k / 5.0;
|
||||
p.ageVelocity = sin(id * k * 1.0) * 0.1 + 0.2;
|
||||
}
|
||||
""".trimIndent()
|
||||
computeTransform = """
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
initParticle(id, b_particles.particle[id]);
|
||||
""".trimIndent()
|
||||
workGroupSize = IntVector3(32, 1, 1)
|
||||
}
|
||||
val updateCS = computeStyle {
|
||||
computePreamble = """
|
||||
void updateParticle(inout Particle p) {
|
||||
// Add velocity to position
|
||||
p.pos += p.velocity;
|
||||
|
||||
// Update age
|
||||
p.age += p.ageVelocity;
|
||||
|
||||
// Deal with the particle trying to escape the window
|
||||
if(p.pos.x < 0.0) {
|
||||
p.pos.x += p_windowSize.x;
|
||||
}
|
||||
if(p.pos.y < 0.0) {
|
||||
p.pos.y += p_windowSize.y;
|
||||
}
|
||||
if(p.pos.x > p_windowSize.x) {
|
||||
p.pos.x = 0.0;
|
||||
}
|
||||
if(p.pos.y > p_windowSize.y) {
|
||||
p.pos.y = 0.0;
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
computeTransform = """
|
||||
// The id of the element being currently processed
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
updateParticle(b_particles.particle[id]);
|
||||
|
||||
float alpha = sin(b_particles.particle[id].age);
|
||||
alpha = sin(alpha + sin(alpha)) * 0.5 + 0.5;
|
||||
|
||||
// draw particle in the image
|
||||
imageStore(p_img,
|
||||
ivec2(b_particles.particle[id].pos),
|
||||
vec4((b_particles.particle[id].color * alpha), alpha)
|
||||
);
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
// Execute initCS
|
||||
initCS.buffer("particles", particleSSBO)
|
||||
initCS.execute(particleCount / initCS.workGroupSize.x)
|
||||
|
||||
extend {
|
||||
// Clear the image, otherwise all pixels become eventually white
|
||||
cb.fill(ColorRGBa.TRANSPARENT)
|
||||
|
||||
// Pass image to the compute shader.
|
||||
// We can choose between READ, READ_WRITE or WRITE.
|
||||
updateCS.image("img", cb.imageBinding(0, ImageAccess.WRITE))
|
||||
updateCS.buffer("particles", particleSSBO)
|
||||
updateCS.parameter("windowSize", drawer.bounds.dimensions)
|
||||
updateCS.execute(particleCount / updateCS.workGroupSize.x)
|
||||
|
||||
drawer.image(cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
128
openrndr-demos/src/demo/kotlin/DemoComputeStyle08.kt
Normal file
128
openrndr-demos/src/demo/kotlin/DemoComputeStyle08.kt
Normal file
@@ -0,0 +1,128 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.*
|
||||
|
||||
/**
|
||||
* This program demonstrates
|
||||
* - How to animate 2D particles but render them as 2D triangles.
|
||||
* - How to deal with two buffers of different size: the particles buffer
|
||||
* and the vertex buffer, which has three times more elements.
|
||||
* The update compute shader calculates 3 vertices for each particle.
|
||||
* - How to make the init compute shader initialize both the particles (position and velocity)
|
||||
* and the colors of the vertices.
|
||||
* - How to create a minimal ShadeStyle to set per-vertex colors,
|
||||
* and render the colors interpolated across each triangle.
|
||||
*
|
||||
* Output: vertexBuffer -> TRIANGLES
|
||||
*/
|
||||
|
||||
fun main() = application {
|
||||
program {
|
||||
val particleCount = 3200
|
||||
|
||||
// Define SSBO format
|
||||
val ssboFmt = shaderStorageFormat {
|
||||
struct("Particle", "particle", particleCount) {
|
||||
primitive("pos", BufferPrimitiveType.VECTOR2_FLOAT32)
|
||||
primitive("velocity", BufferPrimitiveType.VECTOR2_FLOAT32)
|
||||
}
|
||||
}
|
||||
println("Study the padding in the format:\n$ssboFmt\n")
|
||||
|
||||
// Create SSBO
|
||||
val particleSSBO = shaderStorageBuffer(ssboFmt)
|
||||
|
||||
// The padding is required to match the expected layout.
|
||||
// Even if we are in a 2D world we need to use a 3D position
|
||||
// because that's what OPENRNDR expects in its shaders.
|
||||
val vertFormat = vertexFormat(BufferAlignment.STD430) {
|
||||
position(3)
|
||||
color(4)
|
||||
}
|
||||
println("Study the padding in the vertex buffer format:\n$vertFormat\n")
|
||||
|
||||
// Create vertex buffer.
|
||||
// Note how me multiply the particleCount by 3 (three vertices per particle).
|
||||
val vb = vertexBuffer(vertFormat, particleCount * 3)
|
||||
|
||||
// Create Compute Shaders
|
||||
val initCS = computeStyle {
|
||||
computeTransform = """
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
|
||||
b_particles.particle[id].pos = vec2(320.0, 240.0);
|
||||
b_particles.particle[id].velocity = vec2(cos(id), sin(id));
|
||||
|
||||
// Generate colors based on id
|
||||
vec4 col = vec4(
|
||||
sin(id + 0.000) * 0.5 + 0.5,
|
||||
sin(id + 2.094) * 0.5 + 0.5,
|
||||
sin(id + 4.188) * 0.5 + 0.5, 1.0);
|
||||
|
||||
// Swap R, G and B and darken the two rear vertices of each triangle.
|
||||
// This creates a gradient in each triangle.
|
||||
b_vb.vertex[id * 3 + 0].color = col.rgba;
|
||||
b_vb.vertex[id * 3 + 1].color = col.grba * 0.5;
|
||||
b_vb.vertex[id * 3 + 2].color = col.rbga * 0.25;
|
||||
""".trimIndent()
|
||||
}
|
||||
val updateCS = computeStyle {
|
||||
computePreamble = """
|
||||
const float margin = 16.0;
|
||||
void updateParticle(inout Particle p) {
|
||||
// Add velocity to position
|
||||
p.pos += p.velocity;
|
||||
|
||||
// Deal with the particle trying to escape the window
|
||||
if(p.pos.x < -margin) {
|
||||
p.pos.x += p_windowSize.x + 2.0 * margin;
|
||||
}
|
||||
if(p.pos.y < -margin) {
|
||||
p.pos.y += p_windowSize.y + 2.0 * margin;
|
||||
}
|
||||
if(p.pos.x > p_windowSize.x + margin) {
|
||||
p.pos.x = -margin;
|
||||
}
|
||||
if(p.pos.y > p_windowSize.y + margin) {
|
||||
p.pos.y = -margin;
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
computeTransform = """
|
||||
// The id of the element being currently processed
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
updateParticle(b_particles.particle[id]);
|
||||
|
||||
// Calculate the vertices of a directed triangle
|
||||
// pointing towards `velocity`. Hint:
|
||||
// vel (x,y) has two normals (-y, x) and (y, -x).
|
||||
vec2 pos = b_particles.particle[id].pos;
|
||||
vec2 vel = b_particles.particle[id].velocity * margin;
|
||||
vec2 n0 = vec2(-vel.y, vel.x) * 0.5;
|
||||
b_vb.vertex[id * 3 + 0].position = vec3(pos + vel, 0.0);
|
||||
b_vb.vertex[id * 3 + 1].position = vec3(pos + n0, 0.0);
|
||||
b_vb.vertex[id * 3 + 2].position = vec3(pos - n0, 0.0);
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
// Execute initCS
|
||||
initCS.buffer("vb", vb.shaderStorageBufferView())
|
||||
initCS.buffer("particles", particleSSBO)
|
||||
initCS.execute(particleCount / initCS.workGroupSize.x)
|
||||
|
||||
extend {
|
||||
updateCS.buffer("vb", vb.shaderStorageBufferView())
|
||||
updateCS.buffer("particles", particleSSBO)
|
||||
updateCS.parameter("windowSize", drawer.bounds.dimensions)
|
||||
updateCS.execute(particleCount / updateCS.workGroupSize.x)
|
||||
|
||||
drawer.fill = ColorRGBa.WHITE
|
||||
drawer.shadeStyle = shadeStyle {
|
||||
// The color of every triangle's pixel is interpolated using
|
||||
// its three vertex colors
|
||||
fragmentTransform = "x_fill = va_color;"
|
||||
}
|
||||
drawer.vertexBuffer(vb, DrawPrimitive.TRIANGLES)
|
||||
}
|
||||
}
|
||||
}
|
||||
80
openrndr-demos/src/demo/kotlin/DemoComputeStyle09.kt
Normal file
80
openrndr-demos/src/demo/kotlin/DemoComputeStyle09.kt
Normal file
@@ -0,0 +1,80 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.DrawPrimitive
|
||||
import org.openrndr.draw.computeStyle
|
||||
import org.openrndr.draw.execute
|
||||
import org.openrndr.draw.shadeStyle
|
||||
import org.openrndr.extra.camera.Orbital
|
||||
import org.openrndr.extra.meshgenerators.dodecahedronMesh
|
||||
import org.openrndr.math.IntVector3
|
||||
import org.openrndr.math.Vector3
|
||||
|
||||
/**
|
||||
* This program demonstrates
|
||||
* - How to use a compute shader to deform a 3D shape (a vertex buffer).
|
||||
* We create a dodecahedron vertex buffer and displace its vertices
|
||||
* along their normals, exploding the object into 12 pieces with
|
||||
* 3 triangles each (3 triangles to form a pentagon).
|
||||
*
|
||||
* Use the mouse for panning, rotating and zooming.
|
||||
*
|
||||
* Output: vertexBuffer -> TRIANGLES
|
||||
*/
|
||||
|
||||
fun main() = application {
|
||||
program {
|
||||
val vb = dodecahedronMesh(2.0)
|
||||
|
||||
//vb.saveOBJ("/tmp/dodecahedron.obj") // study with Blender3D
|
||||
|
||||
println(vb.vertexFormat.toString().replace("), ", "),\n ").replace("[", "[\n ").replace("]", "\n]"))
|
||||
println("Vertex count: ${vb.vertexCount}")
|
||||
|
||||
// Create Compute Shaders
|
||||
val updateCS = computeStyle {
|
||||
computeTransform = """
|
||||
// The id of the element being currently processed
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
|
||||
b_vb.vertex[id].position += b_vb.vertex[id].normal * 0.01;
|
||||
""".trimIndent()
|
||||
workGroupSize = IntVector3(64, 1, 1)
|
||||
}
|
||||
|
||||
// Debugging: print the mesh data
|
||||
val shadow = vb.shadow
|
||||
shadow.download()
|
||||
val reader = shadow.reader()
|
||||
reader.rewind()
|
||||
repeat(vb.vertexCount) {
|
||||
println(it)
|
||||
// Notice how we read Vector4's instead of Vector3 or Vector2
|
||||
// because the data has been padded to align 16-byte boundaries.
|
||||
val pos = reader.readVector4()
|
||||
val nrm = reader.readVector4()
|
||||
val uv = reader.readVector4()
|
||||
println(" pos: ${pos.xyz}")
|
||||
println(" nrm: ${nrm.xyz}")
|
||||
println(" uv: ${uv.xy}")
|
||||
}
|
||||
|
||||
val cam = Orbital()
|
||||
cam.eye = Vector3.UNIT_Z * 5.0
|
||||
|
||||
extend(cam)
|
||||
extend {
|
||||
updateCS.buffer("vb", vb.shaderStorageBufferView())
|
||||
// We use a width of 2 because we have 108 vertices and the
|
||||
// workgroup size is 64. 2 x 64 = 128, which is greater than 108 and
|
||||
// therefore processes all the vertices.
|
||||
updateCS.execute(2)
|
||||
|
||||
drawer.clear(ColorRGBa.GRAY)
|
||||
drawer.fill = ColorRGBa.WHITE
|
||||
drawer.shadeStyle = shadeStyle {
|
||||
fragmentTransform = "x_fill.rgb = va_normal.xyz * 0.5 + 0.5;"
|
||||
}
|
||||
drawer.vertexBuffer(vb, DrawPrimitive.TRIANGLES)
|
||||
}
|
||||
}
|
||||
}
|
||||
88
openrndr-demos/src/demo/kotlin/DemoComputeStyle10.kt
Normal file
88
openrndr-demos/src/demo/kotlin/DemoComputeStyle10.kt
Normal file
@@ -0,0 +1,88 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.DrawPrimitive
|
||||
import org.openrndr.draw.computeStyle
|
||||
import org.openrndr.draw.execute
|
||||
import org.openrndr.draw.shadeStyle
|
||||
import org.openrndr.extra.camera.Orbital
|
||||
import org.openrndr.extra.color.presets.WHEAT
|
||||
import org.openrndr.extra.meshgenerators.dodecahedronMesh
|
||||
import org.openrndr.math.IntVector3
|
||||
import org.openrndr.math.Vector3
|
||||
|
||||
/**
|
||||
* This program is a variation of compute09.kt. It draws `vb`
|
||||
* multiple times, each with a unique translation and rotation.
|
||||
*
|
||||
* For each item drawn, the instance number is sent to the shade style
|
||||
* as a float uniform (named `p_i`) to shade them with unique hues.
|
||||
* The interpolated normal varying is used to set the color, and
|
||||
* this color rotated using `p_i` as the rotation angle.
|
||||
*
|
||||
* Use the mouse for panning, rotating and zooming.
|
||||
*
|
||||
* Output: vertexBuffer -> TRIANGLES
|
||||
*/
|
||||
|
||||
fun main() = application {
|
||||
program {
|
||||
val vb = dodecahedronMesh(2.0)
|
||||
|
||||
// Create Compute Shaders
|
||||
val updateCS = computeStyle {
|
||||
computeTransform = """
|
||||
// The id of the element being currently processed
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
|
||||
b_vb.vertex[id].position += b_vb.vertex[id].normal * 0.01;
|
||||
""".trimIndent()
|
||||
workGroupSize = IntVector3(64, 1, 1)
|
||||
}
|
||||
|
||||
val cam = Orbital()
|
||||
cam.eye = Vector3.UNIT_Z * 5.0
|
||||
|
||||
val style = shadeStyle {
|
||||
// From https://github.com/dmnsgn/glsl-rotate
|
||||
fragmentPreamble = """
|
||||
mat4 rotation3d(vec3 axis, float angle) {
|
||||
axis = normalize(axis);
|
||||
float s = sin(angle);
|
||||
float c = cos(angle);
|
||||
float oc = 1.0 - c;
|
||||
|
||||
return mat4(
|
||||
oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,
|
||||
oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,
|
||||
oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0
|
||||
);
|
||||
}
|
||||
|
||||
vec3 rotate(vec3 v, vec3 axis, float angle) {
|
||||
return (rotation3d(axis, angle) * vec4(v, 1.0)).xyz;
|
||||
}
|
||||
""".trimIndent()
|
||||
fragmentTransform = """
|
||||
x_fill.rgb = rotate(va_normal.xyz * 0.5 + 0.5,
|
||||
normalize(vec3(1.0)), p_i);
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
extend(cam)
|
||||
extend {
|
||||
updateCS.buffer("vb", vb.shaderStorageBufferView())
|
||||
updateCS.execute(2)
|
||||
|
||||
drawer.clear(ColorRGBa.WHEAT.shade(0.2))
|
||||
drawer.fill = ColorRGBa.WHITE
|
||||
drawer.shadeStyle = style
|
||||
repeat(10) {
|
||||
style.parameter("i", it * 0.3)
|
||||
drawer.translate(1.0, 0.0, 0.0)
|
||||
drawer.rotate(Vector3.UNIT_Z, 5.0)
|
||||
drawer.vertexBuffer(vb, DrawPrimitive.TRIANGLES)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
openrndr-demos/src/demo/kotlin/DemoComputeStyle20.kt
Normal file
61
openrndr-demos/src/demo/kotlin/DemoComputeStyle20.kt
Normal file
@@ -0,0 +1,61 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.math.IntVector3
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
* Use a compute shader to read from a colorBuffer
|
||||
* and write into a different colorBuffer.
|
||||
*
|
||||
* The input colorBuffer is updated on every animation frame
|
||||
* with a scaling circle.
|
||||
*
|
||||
* Then the compute shader is executed to update every pixel
|
||||
* in the output colorBuffer by reading a displaced pixel
|
||||
* from the input colorBuffer.
|
||||
*
|
||||
* Output: 2D Image
|
||||
*/
|
||||
fun main() {
|
||||
application {
|
||||
program {
|
||||
val input = renderTarget(width, height) {
|
||||
colorBuffer(type = ColorType.FLOAT32)
|
||||
}
|
||||
val output = input.colorBuffer(0).createEquivalent()
|
||||
|
||||
val cs = computeStyle {
|
||||
computeTransform = """
|
||||
ivec2 id = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 src = ivec2(id + sin(id) * p_m);
|
||||
vec4 c = imageLoad(p_inputImage, src);
|
||||
imageStore(p_outputImage, id, c);
|
||||
""".trimIndent()
|
||||
workGroupSize = IntVector3(16, 16, 1)
|
||||
}
|
||||
|
||||
cs.image("inputImage", input.colorBuffer(0).imageBinding(0, ImageAccess.READ))
|
||||
cs.image("outputImage", output.imageBinding(0, ImageAccess.WRITE))
|
||||
|
||||
extend {
|
||||
// Update input
|
||||
drawer.isolatedWithTarget(input) {
|
||||
clear(ColorRGBa.TRANSPARENT)
|
||||
circle(bounds.center, 100.0 + 80 * sin(seconds))
|
||||
}
|
||||
|
||||
// Apply the compute shader to update output
|
||||
cs.parameter("m", sin(seconds * 0.8) * 13.0 + 15.0)
|
||||
cs.execute(
|
||||
output.width / cs.workGroupSize.x,
|
||||
output.height / cs.workGroupSize.y,
|
||||
1
|
||||
)
|
||||
|
||||
// Draw result
|
||||
drawer.image(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user