├── .gitignore ├── .gitmodules ├── .vscode └── launch.json ├── LICENSE.md ├── README.md ├── Sources ├── Cylinder.hx ├── CylinderMesh.hx ├── InstancedExample.hx ├── Main.hx └── Shaders │ ├── simple.frag.glsl │ └── simple.vert.glsl ├── build-web.bat ├── build-win.bat └── khafile.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /build 3 | /korefile.js 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Kha"] 2 | path = Kha 3 | url = https://github.com/KTXSoftware/Kha 4 | branch = master 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "electron", 5 | "request": "launch", 6 | "name": "Kha: HTML5", 7 | "appDir": "${workspaceFolder}/build/debug-html5", 8 | "sourceMaps": true, 9 | "preLaunchTask": "Kha: Build for Debug HTML5" 10 | }, 11 | { 12 | "type": "krom", 13 | "request": "launch", 14 | "name": "Kha: Krom", 15 | "preLaunchTask": "Kha: Build for Krom" 16 | } 17 | ], 18 | "compounds": [] 19 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Christian Reuter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kha-InstancedExample 2 | Simple example for instanced rendering in Kha. Preview: http://www.aoe-maniac.de/instances/ 3 | 4 | Instanced rendering allows you to render an object multiple times with some variations, but in a singe drawcall. An example for this are lots of identical blocks with different positions. Rendering them individually there is lots of communication overhead between CPU and GPU. But please keep in mind that this only makes sense if you are drawing many instances of a single model. 5 | 6 | There are 10.000 instances consiting of 128 polyons generated by default, both values can be tweaked easily. 7 | -------------------------------------------------------------------------------- /Sources/Cylinder.hx: -------------------------------------------------------------------------------- 1 | package; 2 | import kha.math.Matrix4; 3 | import kha.math.Vector3; 4 | import kha.Scheduler; 5 | 6 | // Data container for cylinder instances 7 | class Cylinder { 8 | 9 | private var amp : Float; 10 | private var phase : Float; 11 | private var yOffset : Float; 12 | private var position : Vector3; 13 | 14 | public function new(amp : Float, phase : Float, position : Vector3) { 15 | this.amp = amp; 16 | this.phase = phase; 17 | this.position = position; 18 | } 19 | 20 | public function getModelMatrix() : Matrix4 { 21 | return Matrix4.translation(position.x, position.y + yOffset, position.z); 22 | } 23 | 24 | public function update() { 25 | // Update position over time 26 | yOffset = amp * Math.sin(position.x * 4 + position.z + Scheduler.time() * 2 * phase) / 4; 27 | } 28 | } -------------------------------------------------------------------------------- /Sources/CylinderMesh.hx: -------------------------------------------------------------------------------- 1 | package; 2 | import kha.math.Matrix3; 3 | import kha.math.Vector2; 4 | 5 | // Generates index and vertex data for a cylinder 6 | class CylinderMesh { 7 | 8 | public var vertices: Array; 9 | public var indices: Array; 10 | 11 | public function new(sections : Int) { 12 | // Radius 13 | var r : Float = 0.5; 14 | // Height 15 | var h : Float = 1; 16 | 17 | vertices = new Array(); 18 | indices = new Array(); 19 | 20 | // Bottom center 21 | vertices.push(0); 22 | vertices.push(0); 23 | vertices.push(0); 24 | 25 | // Top center 26 | vertices.push(0); 27 | vertices.push(h); 28 | vertices.push(0); 29 | 30 | var index : Int = 2; 31 | var firstPoint : Vector2 = new Vector2(0, r); 32 | var lastPoint : Vector2 = firstPoint; 33 | var nextPoint : Vector2; 34 | for (i in 0...sections) { 35 | nextPoint = Matrix3.rotation(i * (2 / sections) * Math.PI).multvec(firstPoint); 36 | 37 | addSection(lastPoint, nextPoint, h, index); 38 | 39 | lastPoint = nextPoint; 40 | index += 4; 41 | } 42 | 43 | addSection(lastPoint, firstPoint, h, index); 44 | 45 | // Last part, close exactly (i.e. use first point) 46 | 47 | // Simple triangle 48 | /*vertices = [ 49 | -1.0, -1.0, 0.0, 50 | 0.0, -1.0, 0.0, 51 | 0.0, 0.0, r 52 | ]; 53 | indices = [ 54 | 0, 55 | 1, 56 | 2 57 | ];*/ 58 | } 59 | 60 | private function addSection(lastPoint : Vector2, nextPoint : Vector2, h : Float, index : Int) { 61 | vertices.push(lastPoint.x); 62 | vertices.push(0); 63 | vertices.push(lastPoint.y); 64 | 65 | vertices.push(lastPoint.x); 66 | vertices.push(h); 67 | vertices.push(lastPoint.y); 68 | 69 | vertices.push(nextPoint.x); 70 | vertices.push(0); 71 | vertices.push(nextPoint.y); 72 | 73 | vertices.push(nextPoint.x); 74 | vertices.push(h); 75 | vertices.push(nextPoint.y); 76 | 77 | // First part of side 78 | indices.push(index); 79 | indices.push(index + 1); 80 | indices.push(index + 2); 81 | 82 | // Second part of side 83 | indices.push(index + 3); 84 | indices.push(index + 2); 85 | indices.push(index + 1); 86 | 87 | // Bottom 88 | indices.push(0); 89 | indices.push(index); 90 | indices.push(index + 2); 91 | 92 | // Top 93 | indices.push(index + 3); 94 | indices.push(index + 1); 95 | indices.push(1); 96 | } 97 | } -------------------------------------------------------------------------------- /Sources/InstancedExample.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import kha.Framebuffer; 4 | import kha.Color; 5 | import kha.graphics4.CullMode; 6 | import kha.math.Random; 7 | import kha.math.Vector3; 8 | import kha.math.Vector4; 9 | import kha.Scheduler; 10 | import kha.Shaders; 11 | import kha.graphics4.CompareMode; 12 | import kha.graphics4.IndexBuffer; 13 | import kha.graphics4.PipelineState; 14 | import kha.graphics4.Usage; 15 | import kha.graphics4.VertexBuffer; 16 | import kha.graphics4.VertexData; 17 | import kha.graphics4.VertexStructure; 18 | import kha.math.Matrix4; 19 | 20 | class InstancedExample { 21 | 22 | // Generate 100x100 instances 23 | static var instancesX = 100; 24 | static var instancesZ = 100; 25 | 26 | // Each cylinder has 32 sides generated, change to control level of detail / polygon count 27 | static var cylinderSections = 32; // Each section results in 4 polygons 28 | 29 | var cameraStart : Vector4; 30 | var view : Matrix4; 31 | var projection : Matrix4; 32 | var mvp : Matrix4; 33 | 34 | var instances : Array; 35 | 36 | var vertexBuffers: Array; 37 | var indexBuffer: IndexBuffer; 38 | var pipeline: PipelineState; 39 | 40 | public function new() { 41 | Random.init(Std.random(403)); 42 | 43 | // Initialize data, not relevant for rendering 44 | instances = new Array(); 45 | for (x in 0...instancesX) { 46 | for (z in 0...instancesZ) { 47 | // Span x/z grid, center on 0/0 48 | var pos = new Vector3(x - (instancesX - 1) / 2, 0, z - (instancesZ - 1) / 2); 49 | instances.push(new Cylinder(1 + (Random.getIn(-25, 25) / 100), 1 + (Random.getIn(-25, 25) / 100), pos)); 50 | } 51 | } 52 | cameraStart = new Vector4(0, 5, 7.5); 53 | 54 | // Set up static projection matrix 55 | projection = Matrix4.perspectiveProjection(45.0, 4.0 / 3.0, 0.1, 100.0); 56 | 57 | var structures = new Array(); 58 | 59 | // Mesh structure, is shared by all instances 60 | var mesh : CylinderMesh = new CylinderMesh(cylinderSections); 61 | structures[0] = new VertexStructure(); 62 | structures[0].add("pos", VertexData.Float3); 63 | 64 | // Vertex buffer 65 | vertexBuffers = new Array(); 66 | vertexBuffers[0] = new VertexBuffer( 67 | Std.int(mesh.vertices.length / 3), 68 | structures[0], 69 | Usage.StaticUsage 70 | ); 71 | 72 | var vbData = vertexBuffers[0].lock(); 73 | for (i in 0...vbData.length) { 74 | vbData.set(i, mesh.vertices[i]); 75 | } 76 | vertexBuffers[0].unlock(); 77 | 78 | // Index buffer 79 | indexBuffer = new IndexBuffer( 80 | mesh.indices.length, 81 | Usage.StaticUsage 82 | ); 83 | 84 | var iData = indexBuffer.lock(); 85 | for (i in 0...iData.length) { 86 | iData[i] = mesh.indices[i]; 87 | } 88 | indexBuffer.unlock(); 89 | 90 | // Color structure, is different for each instance 91 | structures[1] = new VertexStructure(); 92 | structures[1].add("col", VertexData.Float3); 93 | 94 | vertexBuffers[1] = new VertexBuffer( 95 | instances.length, 96 | structures[1], 97 | Usage.StaticUsage, 98 | 1 // changed after every instance, use i higher number for repetitions 99 | ); 100 | 101 | var oData = vertexBuffers[1].lock(); 102 | for (i in 0...instances.length) { 103 | oData.set(i * 3, 1); 104 | oData.set(i * 3 + 1, 0.75 + Random.getIn(-100, 100) / 500); 105 | oData.set(i * 3 + 2, 0); 106 | } 107 | vertexBuffers[1].unlock(); 108 | 109 | // Transformation matrix, is different for each instance 110 | structures[2] = new VertexStructure(); 111 | structures[2].add("m", VertexData.Float4x4); 112 | 113 | vertexBuffers[2] = new VertexBuffer( 114 | instances.length, 115 | structures[2], 116 | Usage.StaticUsage, 117 | 1 // changed after every instance, use i higher number for repetitions 118 | ); 119 | // Transformation matrix, buffer is not filled during initialization, but updated for each frame 120 | 121 | // Setup pipeline 122 | pipeline = new PipelineState(); 123 | pipeline.fragmentShader = Shaders.simple_frag; 124 | pipeline.vertexShader = Shaders.simple_vert; 125 | pipeline.inputLayout = structures; 126 | pipeline.depthWrite = true; 127 | pipeline.depthMode = CompareMode.Less; 128 | pipeline.cullMode = CullMode.CounterClockwise; 129 | pipeline.compile(); 130 | } 131 | 132 | public function render(frame: Framebuffer) { 133 | var g = frame.g4; 134 | 135 | // Move camera and update view matrix 136 | var newCameraPos = Matrix4.rotationY(Scheduler.time() / 4).multvec(cameraStart); 137 | view = Matrix4.lookAt(new Vector3(newCameraPos.x, newCameraPos.y, newCameraPos.z), // Position in World Space 138 | new Vector3(0, 0, 0), // Looks at the origin 139 | new Vector3(0, 1, 0) // Up-vector 140 | ); 141 | 142 | var vp = Matrix4.identity(); 143 | vp = vp.multmat(projection); 144 | vp = vp.multmat(view); 145 | 146 | // Fill transformation matrix buffer with values from each instance 147 | var mData = vertexBuffers[2].lock(); 148 | for (i in 0...instances.length) { 149 | mvp = vp.multmat(instances[i].getModelMatrix()); 150 | 151 | mData.set(i * 16 + 0, mvp._00); 152 | mData.set(i * 16 + 1, mvp._01); 153 | mData.set(i * 16 + 2, mvp._02); 154 | mData.set(i * 16 + 3, mvp._03); 155 | 156 | mData.set(i * 16 + 4, mvp._10); 157 | mData.set(i * 16 + 5, mvp._11); 158 | mData.set(i * 16 + 6, mvp._12); 159 | mData.set(i * 16 + 7, mvp._13); 160 | 161 | mData.set(i * 16 + 8, mvp._20); 162 | mData.set(i * 16 + 9, mvp._21); 163 | mData.set(i * 16 + 10, mvp._22); 164 | mData.set(i * 16 + 11, mvp._23); 165 | 166 | mData.set(i * 16 + 12, mvp._30); 167 | mData.set(i * 16 + 13, mvp._31); 168 | mData.set(i * 16 + 14, mvp._32); 169 | mData.set(i * 16 + 15, mvp._33); 170 | } 171 | vertexBuffers[2].unlock(); 172 | 173 | g.begin(); 174 | g.clear(Color.fromFloats(1, 0.75, 0), 1.0); 175 | g.setPipeline(pipeline); 176 | 177 | // Instanced rendering 178 | if (g.instancedRenderingAvailable()) { 179 | g.setVertexBuffers(vertexBuffers); 180 | g.setIndexBuffer(indexBuffer); 181 | g.drawIndexedVerticesInstanced(instances.length); 182 | } 183 | else { 184 | // TODO: You should define an alternative as there might be older systems that do not support this extension! 185 | //for (i in 0...3) { 186 | //g.setFloat3(col, ...); 187 | //g.setMatrix(m, ...); 188 | //g.setVertexBuffer(vertexBuffer); 189 | //g.setIndexBuffer(indexBuffer); 190 | 191 | // g.drawIndexedVertices(); 192 | //} 193 | } 194 | 195 | g.end(); 196 | } 197 | 198 | public function update() { 199 | // Updated cylinders 200 | for (i in 0...instances.length) { 201 | instances[i].update(); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /Sources/Main.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import kha.Scheduler; 4 | import kha.System; 5 | 6 | class Main { 7 | 8 | public static function main() { 9 | System.start({title: "InstancedExample", width: 800, height: 600}, function (_) { 10 | init(); 11 | }); 12 | } 13 | 14 | static function init() { 15 | var game = new InstancedExample(); 16 | Scheduler.addTimeTask(game.update, 0, 1 / 60); 17 | System.notifyOnFrames(function (frames) { game.render(frames[0]); }); 18 | } 19 | } -------------------------------------------------------------------------------- /Sources/Shaders/simple.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | in vec3 fragmentColor; 4 | out vec4 frag; 5 | 6 | void main() { 7 | frag = vec4(fragmentColor, 1.0); 8 | } -------------------------------------------------------------------------------- /Sources/Shaders/simple.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | // Input vertex data, different for all executions of this shader 4 | in vec3 pos; 5 | 6 | // Instanced input data (different for each instance but the same for each vertex of an instance) 7 | in vec3 col; 8 | in mat4 m; 9 | 10 | // Output data - will be interpolated for each fragment 11 | out vec3 fragmentColor; 12 | 13 | void main() { 14 | gl_Position = m * vec4(pos, 1.0); 15 | fragmentColor = col; 16 | } -------------------------------------------------------------------------------- /build-web.bat: -------------------------------------------------------------------------------- 1 | node Kha/make html5 2 | PAUSE -------------------------------------------------------------------------------- /build-win.bat: -------------------------------------------------------------------------------- 1 | node Kha/make windows --visualstudio vs2013 --graphics opengl2 2 | PAUSE -------------------------------------------------------------------------------- /khafile.js: -------------------------------------------------------------------------------- 1 | let project = new Project('Instanced Example'); 2 | 3 | project.addSources('Sources'); 4 | project.addShaders('Sources/Shaders/**'); 5 | 6 | resolve(project); 7 | --------------------------------------------------------------------------------