├── .gitignore ├── Readme.md ├── build.gradle └── src ├── 1-OpeningAWindow └── main.kt ├── 2-HelloTriangle └── main.kt ├── 3-Matrices └── main.kt ├── 5-ATexturedCube └── main.kt ├── common ├── Matrix4x4.kt ├── Vector3.kt ├── Vector4.kt └── common.kt ├── dependencies ├── c │ └── stb_image.h ├── cglew.def ├── cglfw.def └── cstb.def └── resources ├── 2 ├── SimpleFragmentShader.glsl └── SimpleVertexShader.glsl ├── 3 ├── SimpleTransform.glsl └── SingleColor.glsl └── 5 ├── fragment.glsl ├── logo.png ├── smiley.png └── vertex.glsl /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/macos 2 | 3 | ### macOS ### 4 | *.DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | 31 | # End of https://www.gitignore.io/api/macos 32 | 33 | build 34 | .gradle 35 | .idea 36 | gradle 37 | gradlew 38 | gradlew.bat 39 | cmake-build-debug 40 | KotlinCMakeModule 41 | CMakeLists.txt 42 | *.iml -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # OpenGL tutorial written in Kotlin Native 2 | 3 | The tutorial could be found [here](http://opengl-tutorial.org). 4 | 5 | ## Requirementes 6 | 7 | - [GLFW](http://www.glfw.org) 8 | - [GLEW](http://glew.sourceforge.net) 9 | 10 | **MacOS** 11 | 12 | ``` 13 | brew install glfw glew 14 | ``` 15 | 16 | **Ubuntu** 17 | 18 | *Not tested* 19 | 20 | ``` 21 | sudo apt-get install libglfw3 libglfw3-dev libglew-dev gcc-multilib g++-multilib 22 | ``` 23 | 24 | ## Running 25 | 26 | This will build and run every example. 27 | 28 | ``` 29 | gradle build 30 | gradle run 31 | ``` 32 | 33 | *Need to check in the kotlin native gradle docs 34 | how to run only the desired example* 35 | 36 | ## TODO 37 | 38 | **Basic tutorial** 39 | 40 | - [x] 1 - Opening a window 41 | - [x] 2 - Hello Triangle 42 | - [ ] 3 - Matrices 43 | - [ ] 4 - A Colored Cube 44 | - [ ] 5 - A Textured Cube 45 | - [ ] 6 - Keyboard and Mouse 46 | - [ ] 7 - Model loading 47 | - [ ] 8 - Basic shading 48 | 49 | ## CMake script to copy resources 50 | 51 | ```cmake 52 | add_custom_target(copy-resources ALL 53 | COMMAND cmake -E copy_directory ${CMAKE_SOURCE_DIR}/src/resources ${CMAKE_BINARY_DIR}/resources 54 | DEPENDS ${intown}) 55 | ``` -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | maven { 5 | url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies" 6 | } 7 | } 8 | dependencies { 9 | classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:0.7" 10 | } 11 | } 12 | 13 | apply plugin: 'konan' 14 | 15 | konanArtifacts { 16 | interop('cglew') { 17 | defFile 'src/dependencies/cglew.def' 18 | } 19 | 20 | interop('cglfw') { 21 | defFile 'src/dependencies/cglfw.def' 22 | } 23 | 24 | interop('cstb') { 25 | defFile 'src/dependencies/cstb.def' 26 | includeDirs "${project.rootDir}/src/dependencies/c" 27 | } 28 | 29 | library('common') { 30 | srcFiles fileTree('src/common') 31 | } 32 | 33 | program('1-OpeningAWindow') { 34 | srcDir 'src/1-OpeningAWindow' 35 | libraries { 36 | artifact 'cglew' 37 | artifact 'cglfw' 38 | artifact 'common' 39 | } 40 | } 41 | 42 | program('2-HelloTriangle') { 43 | srcDir 'src/2-HelloTriangle' 44 | libraries { 45 | artifact 'cglew' 46 | artifact 'cglfw' 47 | artifact 'common' 48 | } 49 | } 50 | 51 | program('3-Matrices') { 52 | srcDir 'src/3-Matrices' 53 | libraries { 54 | artifact 'cglew' 55 | artifact 'cglfw' 56 | artifact 'common' 57 | } 58 | } 59 | 60 | program('5-A_Textured_Cube') { 61 | srcDir 'src/5-ATexturedCube' 62 | libraries { 63 | artifact 'cglew' 64 | artifact 'cglfw' 65 | artifact 'cstb' 66 | artifact 'common' 67 | } 68 | } 69 | } 70 | 71 | compileKonan { 72 | doLast { 73 | konanArtifacts.'1-OpeningAWindow'.forEach() { task -> 74 | copy { 75 | from 'src/resources' 76 | into "${task.artifact.parentFile}/resources" 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/1-OpeningAWindow/main.kt: -------------------------------------------------------------------------------- 1 | import cglew.glewExperimental 2 | import cglew.glewInit 3 | import cglew.GLEW_OK 4 | import cglew.glClear 5 | import cglfw.* 6 | import kotlinx.cinterop.* 7 | import platform.OpenGL3.* 8 | 9 | fun main(args: Array) { 10 | glewExperimental = GL_TRUE.narrow() 11 | 12 | if (glfwInit() == GL_FALSE) { 13 | throw Error("Failed to initialize GLFW") 14 | } 15 | 16 | glfwWindowHint(GL_SAMPLES, 4) 17 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3) 18 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3) 19 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE) 20 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE) 21 | 22 | val window = glfwCreateWindow(1024, 768, "GLLab", null, null) ?: 23 | throw Error("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.") 24 | 25 | glfwMakeContextCurrent(window) 26 | glewExperimental = GL_TRUE.narrow() 27 | 28 | if (glewInit() != GLEW_OK) { 29 | throw Error("Failed to initialize GLEW") 30 | } 31 | 32 | glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE) 33 | 34 | while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose(window) == 0) { 35 | glfwPollEvents() 36 | glClear(GL_COLOR_BUFFER_BIT) 37 | glfwSwapBuffers(window) 38 | 39 | } 40 | 41 | glfwTerminate() 42 | } -------------------------------------------------------------------------------- /src/2-HelloTriangle/main.kt: -------------------------------------------------------------------------------- 1 | import cglew.glewExperimental 2 | import cglew.glewInit 3 | import cglew.GLEW_OK 4 | import cglew.glClear 5 | import cglfw.* 6 | import kotlinx.cinterop.* 7 | import platform.OpenGL3.* 8 | import common.* 9 | 10 | fun main(args: Array) { 11 | glewExperimental = GL_TRUE.narrow() 12 | 13 | if (glfwInit() == GL_FALSE) { 14 | throw Error("Failed to initialize GLFW") 15 | } 16 | 17 | glfwWindowHint(GL_SAMPLES, 4) 18 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3) 19 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3) 20 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE) 21 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE) 22 | 23 | val window = glfwCreateWindow(1024, 768, "GLLab", null, null) ?: 24 | throw Error("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.") 25 | 26 | glfwMakeContextCurrent(window) 27 | glewExperimental = GL_TRUE.narrow() 28 | 29 | if (glewInit() != GLEW_OK) { 30 | throw Error("Failed to initialize GLEW") 31 | } 32 | 33 | glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE) 34 | 35 | glViewport(0, 0, 1024, 768) 36 | 37 | val program = ShaderProgram("resources/2/SimpleVertexShader.glsl", "resources/2/SimpleFragmentShader.glsl") 38 | 39 | glClearColor(0.2f, 0.3f, 0.3f, 1f) 40 | 41 | val vao: Int = glGenVertexArrays(1) 42 | 43 | glBindVertexArray(vao) 44 | 45 | val vertexBufferData: FloatArray = floatArrayOf( 46 | -0.5f, -0.5f, 0f, 47 | 0.5f, -0.5f, 0f, 48 | 0f, 0.5f, 0f 49 | ) 50 | 51 | val vbo: Int = glGenBuffers(1) 52 | glBindBuffer(GL_ARRAY_BUFFER, vbo) 53 | glBufferData(GL_ARRAY_BUFFER, (vertexBufferData.size * 4).signExtend(), vertexBufferData.refTo(0), GL_STATIC_DRAW) 54 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE.narrow(), 0, null) 55 | glEnableVertexAttribArray(0) 56 | glBindBuffer(GL_ARRAY_BUFFER, 0) 57 | 58 | glBindVertexArray(0) 59 | 60 | while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose(window) == 0) { 61 | glClear(GL_COLOR_BUFFER_BIT) 62 | 63 | program.use() 64 | glBindVertexArray(vao) 65 | glDrawArrays(GL_TRIANGLES, 0, 3) 66 | glBindVertexArray(0) 67 | 68 | glfwSwapBuffers(window) 69 | glfwPollEvents() 70 | } 71 | 72 | glDeleteVertexArrays(1, vao) 73 | glDeleteBuffers(1, vbo) 74 | program.delete() 75 | 76 | glfwTerminate() 77 | } -------------------------------------------------------------------------------- /src/3-Matrices/main.kt: -------------------------------------------------------------------------------- 1 | import cglew.glewExperimental 2 | import cglew.glewInit 3 | import cglew.GLEW_OK 4 | import cglew.glClear 5 | import cglfw.* 6 | import kotlinx.cinterop.* 7 | import platform.OpenGL3.* 8 | import common.* 9 | 10 | fun main(args: Array) { 11 | glewExperimental = GL_TRUE.narrow() 12 | 13 | if (glfwInit() == GL_FALSE) { 14 | throw Error("Failed to initialize GLFW") 15 | } 16 | 17 | glfwWindowHint(GL_SAMPLES, 4) 18 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3) 19 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3) 20 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE) 21 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE) 22 | 23 | val window = glfwCreateWindow(1024, 768, "GLLab", null, null) ?: 24 | throw Error("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.") 25 | 26 | glfwMakeContextCurrent(window) 27 | glewExperimental = GL_TRUE.narrow() 28 | 29 | if (glewInit() != GLEW_OK) { 30 | throw Error("Failed to initialize GLEW") 31 | } 32 | 33 | glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE) 34 | 35 | glViewport(0, 0, 1024, 768) 36 | 37 | val program = ShaderProgram("resources/3/SimpleTransform.glsl", "resources/3/SingleColor.glsl") 38 | 39 | glClearColor(0.2f, 0.3f, 0.3f, 1f) 40 | 41 | val vao: Int = glGenVertexArrays(1) 42 | 43 | glBindVertexArray(vao) 44 | 45 | val matrixID = glGetUniformLocation(program.id, "MVP") 46 | 47 | val projection = Matrix4x4.projection(45f.toRadians(), 4f / 3f, 0.1f, 100f) 48 | val view = Matrix4x4.lookAt( 49 | Vector3(4f, 3f, 3f), 50 | Vector3(0f, 0f, 0f), 51 | Vector3(0f, 1f, 0f) 52 | ) 53 | val model = Matrix4x4.identity 54 | val mvp = projection * view * model 55 | 56 | println("projection $projection") 57 | println("view $view") 58 | println("model $model") 59 | println("mvp $mvp") 60 | 61 | val vertexBufferData: FloatArray = floatArrayOf( 62 | -0.5f, -0.5f, 0f, 63 | 0.5f, -0.5f, 0f, 64 | 0f, 0.5f, 0f 65 | ) 66 | 67 | val vbo: Int = glGenBuffers(1) 68 | glBindBuffer(GL_ARRAY_BUFFER, vbo) 69 | glBufferData(GL_ARRAY_BUFFER, (vertexBufferData.size * 4).signExtend(), vertexBufferData.refTo(0), GL_STATIC_DRAW) 70 | 71 | while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose(window) == 0) { 72 | glClear(GL_COLOR_BUFFER_BIT) 73 | 74 | program.use() 75 | 76 | glUniformMatrix4fv(matrixID, 1, false, mvp.values) 77 | 78 | glEnableVertexAttribArray(0) 79 | glBindBuffer(GL_ARRAY_BUFFER, vbo) 80 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE.narrow(), 0, null) 81 | glDrawArrays(GL_TRIANGLES, 0, 3) 82 | glDisableVertexAttribArray(0) 83 | 84 | glfwSwapBuffers(window) 85 | glfwPollEvents() 86 | } 87 | 88 | glDeleteVertexArrays(1, vao) 89 | glDeleteBuffers(1, vbo) 90 | program.delete() 91 | 92 | glfwTerminate() 93 | } -------------------------------------------------------------------------------- /src/5-ATexturedCube/main.kt: -------------------------------------------------------------------------------- 1 | import cglew.glewExperimental 2 | import cglew.glewInit 3 | import cglew.GLEW_OK 4 | import cglew.glClear 5 | import cglfw.* 6 | import cstb.* 7 | import kotlinx.cinterop.* 8 | import platform.OpenGL3.* 9 | import common.* 10 | 11 | fun main(args: Array) { 12 | glewExperimental = GL_TRUE.narrow() 13 | 14 | if (glfwInit() == GL_FALSE) { 15 | throw Error("Failed to initialize GLFW") 16 | } 17 | 18 | glfwWindowHint(GL_SAMPLES, 4) 19 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3) 20 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3) 21 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE) 22 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE) 23 | 24 | val window = glfwCreateWindow(1024, 768, "GLLab", null, null) ?: 25 | throw Error("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.") 26 | 27 | glfwMakeContextCurrent(window) 28 | glewExperimental = GL_TRUE.narrow() 29 | 30 | if (glewInit() != GLEW_OK) { 31 | throw Error("Failed to initialize GLEW") 32 | } 33 | 34 | glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE) 35 | glfwSetFramebufferSizeCallback(window, staticCFunction(::framebufferSizeCallback)) 36 | 37 | val program = ShaderProgram("resources/5/vertex.glsl", "resources/5/fragment.glsl") 38 | 39 | val vertices: FloatArray = floatArrayOf( 40 | // positions // colors // texture coords 41 | 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right 42 | 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right 43 | -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left 44 | -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left 45 | ) 46 | 47 | val indices: IntArray = intArrayOf( 48 | 0, 1, 3, // first triangle 49 | 1, 2, 3 // second triangle 50 | ) 51 | 52 | val vao: Int = glGenVertexArrays(1) 53 | val vbo: Int = glGenBuffers(1) 54 | val ebo: Int = glGenBuffers(1) 55 | 56 | glBindVertexArray(vao) 57 | 58 | glBindBuffer(GL_ARRAY_BUFFER, vbo) 59 | glBufferData(GL_ARRAY_BUFFER, (vertices.size * FloatVar.size).signExtend(), vertices.refTo(0), GL_STATIC_DRAW) 60 | 61 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) 62 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, (indices.size * FloatVar.size).signExtend(), indices.refTo(0), GL_STATIC_DRAW) 63 | 64 | // position attribute 65 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE.narrow(), (8 * FloatVar.size).toInt(), null) 66 | glEnableVertexAttribArray(0); 67 | // color attribute 68 | val colorStartPosition: Long = 6 * FloatVar.size 69 | glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE.narrow(), (8 * FloatVar.size).toInt(), colorStartPosition.toCPointer()) 70 | glEnableVertexAttribArray(1); 71 | // texture coord attribute 72 | val textureStartPosition: Long = 6 * FloatVar.size 73 | glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE.narrow(), (8 * FloatVar.size).toInt(), textureStartPosition.toCPointer()) 74 | glEnableVertexAttribArray(2); 75 | 76 | stbi_set_flip_vertically_on_load(1) 77 | val texture1: Int = loadTexture("resources/5/logo.png") 78 | val texture2: Int = loadTexture("resources/5/smiley.png") 79 | 80 | // Texture uniforms 81 | program.use() 82 | program.setUniform("texture1", 0) 83 | program.setUniform("texture2", 1) 84 | program.reset() 85 | 86 | while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose(window) == 0) { 87 | glClearColor(0.2f, 0.3f, 0.3f, 1f) 88 | glClear(GL_COLOR_BUFFER_BIT) 89 | 90 | program.use() 91 | 92 | glActiveTexture(GL_TEXTURE0) 93 | glBindTexture(GL_TEXTURE_2D, texture1) 94 | 95 | glActiveTexture(GL_TEXTURE1) 96 | glBindTexture(GL_TEXTURE_2D, texture2) 97 | 98 | glBindVertexArray(vao) 99 | glDrawElements(GL_TRIANGLES, indices.size, GL_UNSIGNED_INT, null) 100 | 101 | glfwSwapBuffers(window) 102 | glfwPollEvents() 103 | } 104 | 105 | glDeleteVertexArrays(1, vao) 106 | glDeleteBuffers(1, vbo) 107 | glDeleteBuffers(1, ebo) 108 | program.delete() 109 | 110 | glfwTerminate() 111 | } 112 | 113 | private fun framebufferSizeCallback(window: CPointer?, width: Int, height: Int) { 114 | glViewport(0, 0, width, height) 115 | } 116 | 117 | private fun loadTexture(filename: String) = memScoped { 118 | val width: IntVar = alloc() 119 | val height: IntVar = alloc() 120 | val channels: IntVar = alloc() 121 | 122 | val data = stbi_load(filename, width.ptr, height.ptr, channels.ptr, 0) 123 | 124 | if (data == null) { 125 | var error = "Error:\n\tdata: null" 126 | if (width.value == 0) error += "\twidth: 0" 127 | if (height.value == 0) error += "\theight: 0" 128 | throw Error(error) 129 | } else { 130 | val textureIdInterop: IntVarOf = alloc() 131 | glGenTextures(1, textureIdInterop.ptr) 132 | val textureId: Int = textureIdInterop.value 133 | 134 | glBindTexture(GL_TEXTURE_2D, textureId) 135 | 136 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT) 137 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) 138 | 139 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 140 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) 141 | 142 | println("Texture $filename ${width.value}x${height.value} (${channels.value})") 143 | val format = if (channels.value == 4) GL_RGBA else GL_RGB 144 | 145 | glTexImage2D(GL_TEXTURE_2D, 0, format, width.value, height.value, 0, format, GL_UNSIGNED_BYTE, data) 146 | 147 | stbi_image_free(data) 148 | 149 | glBindTexture(GL_TEXTURE_2D, 0) 150 | 151 | textureId 152 | } 153 | } -------------------------------------------------------------------------------- /src/common/Matrix4x4.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import kotlin.math.tan 4 | 5 | class Matrix4x4() { 6 | companion object { 7 | val identity: Matrix4x4 get() { 8 | return Matrix4x4( 9 | 1f, 0f, 0f, 0f, 10 | 0f, 1f, 0f, 0f, 11 | 0f, 0f, 1f, 0f, 12 | 0f, 0f, 0f, 1f 13 | ) 14 | } 15 | 16 | fun projection(fovy: Float, aspect: Float, near: Float, far: Float): Matrix4x4 { 17 | val height = 1f / tan(fovy * 0.5f) 18 | val width = height * 1f / aspect 19 | 20 | return projection(0f, 0f, width, height, near, far) 21 | } 22 | 23 | fun projection(x: Float, y: Float, width: Float, height: Float, near: Float, far: Float): Matrix4x4 { 24 | val distance = far - near 25 | val farDivDistance = far / distance 26 | val nearTimesFarDivDistance = near * farDivDistance 27 | 28 | return Matrix4x4( 29 | width, 0f, 0f, 0f, 30 | 0f, height, 0f, 0f, 31 | -x, -y, farDivDistance, -nearTimesFarDivDistance, 32 | 0f, 0f, 1f, 0f 33 | ) 34 | } 35 | 36 | // FIXME: seems that this is wrong, the result differs from others implementations that works 37 | fun lookAt(eye: Vector3, at: Vector3, up: Vector3 = Vector3(0f, 0f, 1f)): Matrix4x4 { 38 | return lookTowards(eye, at - eye, up) 39 | } 40 | 41 | fun lookTowards(eye: Vector3, forward: Vector3, up: Vector3 = Vector3(0f, 0f, 1f)): Matrix4x4 { 42 | val f = forward.normalized 43 | val r = (f * up).normalized 44 | val u = (r * f).normalized 45 | 46 | return Matrix4x4(Vector4(r), Vector4(u), Vector4(f), Vector4(eye, 1f)) 47 | } 48 | } 49 | 50 | var values: FloatArray = floatArrayOf( 51 | 0f, 0f, 0f, 0f, 52 | 0f, 0f, 0f, 0f, 53 | 0f, 0f, 0f, 0f, 54 | 0f, 0f, 0f, 0f 55 | ) 56 | 57 | constructor(vararg values: Float): this() { 58 | if (values.size < 16) throw Error("Matrix contains 16 values, but there was only ${values.size} passed") 59 | this.values = values 60 | } 61 | 62 | constructor(col1: Vector4, col2: Vector4, col3: Vector4, col4: Vector4): this( 63 | col1.x, col1.y, col1.z, col1.w, 64 | col2.x, col2.y, col2.z, col2.w, 65 | col3.x, col3.y, col3.z, col3.w, 66 | col4.x, col4.y, col4.z, col4.w 67 | ) 68 | 69 | operator fun times(vector: Vector4): Vector4 = Vector4( 70 | this[0, 0] * vector.x + this[0, 1] * vector.y + this[0, 2] * vector.z + this[0, 3] * vector.w, 71 | this[1, 0] * vector.x + this[1, 1] * vector.y + this[1, 2] * vector.z + this[1, 3] * vector.w, 72 | this[2, 0] * vector.x + this[2, 1] * vector.y + this[2, 2] * vector.z + this[2, 3] * vector.w, 73 | this[3, 0] * vector.x + this[3, 1] * vector.y + this[3, 2] * vector.z + this[3, 3] * vector.w 74 | ) 75 | 76 | operator fun times(matrix: Matrix4x4): Matrix4x4 { 77 | val t = transpose() 78 | return Matrix4x4( 79 | t[0].dot(matrix[0]), t[1].dot(matrix[0]), t[2].dot(matrix[0]), t[3].dot(matrix[0]), 80 | t[0].dot(matrix[1]), t[1].dot(matrix[1]), t[2].dot(matrix[1]), t[3].dot(matrix[1]), 81 | t[0].dot(matrix[2]), t[1].dot(matrix[2]), t[2].dot(matrix[2]), t[3].dot(matrix[2]), 82 | t[0].dot(matrix[3]), t[1].dot(matrix[3]), t[2].dot(matrix[3]), t[3].dot(matrix[3]) 83 | ) 84 | } 85 | 86 | operator fun get(row: Int): Vector4 = Vector4(this[row, 0], this[row, 1], this[row, 2], this[row, 3]) 87 | 88 | operator fun get(row: Int, col: Int): Float = values[(row * 4) + col] 89 | 90 | operator fun set(row: Int, col: Int, value: Float) { 91 | values[(row * 4) + col] = value 92 | } 93 | 94 | fun transpose(): Matrix4x4 = Matrix4x4( 95 | this[0, 0], this[1, 0], this[2, 0], this[3, 0], 96 | this[0, 1], this[1, 1], this[2, 1], this[3, 1], 97 | this[0, 2], this[1, 2], this[2, 2], this[3, 2], 98 | this[0, 3], this[1, 3], this[2, 3], this[3, 3] 99 | ) 100 | 101 | fun translate(vector: Vector3): Matrix4x4 { 102 | this[3, 0] = vector.x 103 | this[3, 1] = vector.y 104 | this[3, 2] = vector.z 105 | this[3, 3] = 1f 106 | return this 107 | } 108 | 109 | override fun toString(): String { 110 | return """${super.toString()} [ 111 | ${this[0, 0]}, ${this[0, 1]}, ${this[0, 2]}, ${this[0, 3]}, 112 | ${this[1, 0]}, ${this[1, 1]}, ${this[1, 2]}, ${this[1, 3]}, 113 | ${this[2, 0]}, ${this[2, 1]}, ${this[2, 2]}, ${this[2, 3]}, 114 | ${this[3, 0]}, ${this[3, 1]}, ${this[3, 2]}, ${this[3, 3]} 115 | ]""".trimIndent() 116 | } 117 | } -------------------------------------------------------------------------------- /src/common/Vector3.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import kotlin.math.sqrt 4 | 5 | class Vector3(val x: Float, val y: Float, val z: Float) { 6 | constructor(): this(0f, 0f, 0f) 7 | 8 | val lengthSquared: Float = x * x + y * y + z * z 9 | val normalized: Vector3 get() { 10 | val l = 1f / lengthSquared 11 | return Vector3(x * l, y * l, z * l) 12 | } 13 | 14 | fun cross(vector: Vector3): Vector3 = Vector3( 15 | y * vector.z - z * vector.y, z * vector.x - x * vector.z, x * vector.y - y * vector.x 16 | ) 17 | 18 | fun dot(vector: Vector3): Float = x * vector.x + y * vector.y * z * vector.z 19 | 20 | operator fun minus(vector: Vector3): Vector3 = Vector3(x - vector.x, y - vector.y, z - vector.z) 21 | 22 | operator fun div(value: Float): Vector3 = Vector3(x / value, y / value, z / value) 23 | 24 | operator fun times(vector: Vector3): Vector3 = Vector3(x * vector.x, y * vector.y, z * vector.z) 25 | 26 | override fun toString(): String { 27 | return "Vector4($x, $y, $z)" 28 | } 29 | } -------------------------------------------------------------------------------- /src/common/Vector4.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | class Vector4(val x: Float, val y: Float, val z: Float, val w: Float) { 4 | constructor(): this(0f, 0f, 0f, 0f) 5 | constructor(vector: Vector3, w: Float): this(vector.x, vector.y, vector.z, w) 6 | constructor(vector: Vector3) : this(vector.x, vector.y, vector.z, 0f) 7 | 8 | fun dot(vector: Vector4): Float = x * vector.x + y * vector.y + z * vector.z + w * vector.w 9 | 10 | override fun toString(): String { 11 | return "Vector4($x, $y, $z, $w)" 12 | } 13 | } -------------------------------------------------------------------------------- /src/common/common.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import kotlinx.cinterop.* 4 | import platform.OpenGL3.* 5 | import platform.posix.fclose 6 | import platform.posix.fopen 7 | import platform.posix.fread 8 | import platform.posix.stat 9 | import kotlin.math.PI 10 | 11 | fun Float.toRadians(): Float { 12 | return this * PI.toFloat() / 180 13 | } 14 | 15 | fun glGenBuffers(n: Int): Int = memScoped { 16 | val bufferVar: IntVarOf = alloc() 17 | glGenBuffers(n, bufferVar.ptr) 18 | bufferVar.value 19 | } 20 | 21 | fun checkError(message: String?) { 22 | val error = glGetError() 23 | if (error != 0) { 24 | val errorString = when (error) { 25 | GL_INVALID_ENUM -> "GL_INVALID_ENUM" 26 | GL_INVALID_VALUE -> "GL_INVALID_VALUE" 27 | GL_INVALID_OPERATION -> "GL_INVALID_OPERATION" 28 | GL_INVALID_FRAMEBUFFER_OPERATION -> "GL_INVALID_FRAMEBUFFER_OPERATION" 29 | GL_OUT_OF_MEMORY -> "GL_OUT_OF_MEMORY" 30 | else -> "unknown" 31 | } 32 | 33 | if (message != null) println("- $message") 34 | throw Exception("\tGL error: 0x${error.toString(16)} ($errorString)") 35 | } 36 | } 37 | 38 | fun glUniformMatrix4fv(location: Int, count: Int, transpose: Boolean, value: FloatArray) = memScoped { 39 | val _transpose = if (transpose) GL_TRUE else GL_FALSE 40 | platform.OpenGL3.glUniformMatrix4fv(location, count, _transpose.narrow(), value.refTo(0)) 41 | } 42 | 43 | fun glDeleteBuffers(n: Int, buffers: Int) = memScoped { 44 | val int = alloc() 45 | int.value = buffers 46 | platform.OpenGL3.glDeleteBuffers(n, int.ptr) 47 | } 48 | 49 | fun glDeleteVertexArrays(n: Int, arrays: Int) = memScoped { 50 | val int = alloc() 51 | int.value = arrays 52 | platform.OpenGL3.glDeleteVertexArrays(n, int.ptr) 53 | } 54 | 55 | fun glGenVertexArrays(n: Int): Int = memScoped { 56 | val resultVar: IntVarOf = alloc() 57 | glGenVertexArrays(n, resultVar.ptr) 58 | resultVar.value 59 | } 60 | 61 | fun readFile(path: String): String? = memScoped { 62 | val info = alloc() 63 | if (stat(path, info.ptr) != 0) return null 64 | val size = info.st_size.toInt() 65 | val result = ByteArray(size) 66 | val file = fopen(path, "rb") ?: return null 67 | var position = 0 68 | while (position < size) { 69 | val toRead = minOf(size - position, 4096) 70 | val read = fread(result.refTo(position), 1, toRead.signExtend(), file).toInt() 71 | if (read <= 0) break 72 | position += read 73 | } 74 | fclose(file) 75 | return result.stringFromUtf8(0, result.size) 76 | } 77 | 78 | class ShaderProgram(vertex: String, fragment: String, geometry: String? = null) { 79 | var id: Int 80 | private set 81 | 82 | init { 83 | val vertexSource = readFile(vertex) ?: throw Error("File $vertex not found") 84 | val fragmentSource = readFile(fragment) ?: throw Error("File $fragment not found") 85 | val geometrySource = if (geometry != null) readFile(geometry) else null 86 | 87 | val vertexId = compile(GL_VERTEX_SHADER, vertexSource) 88 | val fragmentId = compile(GL_FRAGMENT_SHADER, fragmentSource) 89 | val geometryId = if (geometrySource != null) compile(GL_GEOMETRY_SHADER, geometrySource) else null 90 | 91 | id = glCreateProgram() 92 | 93 | glAttachShader(id, vertexId) 94 | glAttachShader(id, fragmentId) 95 | if (geometryId != null) glAttachShader(id, geometryId) 96 | 97 | glLinkProgram(id) 98 | 99 | checkStatus() 100 | 101 | glDeleteShader(vertexId) 102 | glDeleteShader(fragmentId) 103 | if (geometryId != null) glDeleteShader(geometryId) 104 | } 105 | 106 | fun setUniform(name: String, value: Boolean) { 107 | glUniform1i(glGetUniformLocation(id, name), if (value) 1 else 0) 108 | } 109 | 110 | fun setUniform(name: String, value: Int) { 111 | glUniform1i(glGetUniformLocation(id, name), value) 112 | } 113 | 114 | fun setUniform(name: String, value: Float) { 115 | glUniform1f(glGetUniformLocation(id, name), value) 116 | } 117 | 118 | // TODO: setUniform() -> vector2, vector3, vector4, matrix2, matrix3, matrix4 119 | 120 | fun use() { 121 | glUseProgram(id) 122 | } 123 | 124 | fun reset() { 125 | glUseProgram(0) 126 | } 127 | 128 | fun delete() { 129 | glDeleteProgram(id) 130 | } 131 | 132 | private fun checkStatus() = memScoped { 133 | val status = alloc() 134 | glGetProgramiv(id, GL_LINK_STATUS, status.ptr) 135 | if (status.value != GL_TRUE) { 136 | val log = allocArray(512) 137 | glGetProgramInfoLog(id, 512, null, log) 138 | throw Error("Program linking errors: ${log.toKString()}") 139 | } 140 | } 141 | 142 | private fun compile(type: Int, source: String) = memScoped { 143 | val shader = glCreateShader(type) 144 | 145 | if (shader == 0) throw Error("Failed to create shader") 146 | 147 | glShaderSource(shader, 1, cValuesOf(source.cstr.getPointer(memScope)), null) 148 | glCompileShader(shader) 149 | 150 | val status = alloc() 151 | glGetShaderiv(shader, GL_COMPILE_STATUS, status.ptr) 152 | 153 | if (status.value != GL_TRUE) { 154 | val log = allocArray(512) 155 | glGetShaderInfoLog(shader, 512, null, log) 156 | throw Error("Shader compilation failed: ${log.toKString()}") 157 | } 158 | 159 | checkError("glShaderSource") 160 | 161 | shader 162 | } 163 | } -------------------------------------------------------------------------------- /src/dependencies/cglew.def: -------------------------------------------------------------------------------- 1 | headers = GL/glew.h 2 | headerFilter = GL/* 3 | 4 | compilerOpts.osx = -I/usr/include -I/usr/local/include 5 | linkerOpts.osx = -L/usr/lib -L/usr/local/lib -lglew 6 | compilerOpts.linux = -I/usr/include -I/usr/local/include 7 | linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lglew 8 | -------------------------------------------------------------------------------- /src/dependencies/cglfw.def: -------------------------------------------------------------------------------- 1 | headers = GLFW/glfw3.h 2 | headerFilter = GLFW/* 3 | 4 | compilerOpts.osx = -I/usr/include -I/usr/local/include 5 | linkerOpts.osx = -L/usr/lib -L/usr/local/lib -lglfw 6 | compilerOpts.linux = -I/usr/include -I/usr/local/include 7 | linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lglfw 8 | -------------------------------------------------------------------------------- /src/dependencies/cstb.def: -------------------------------------------------------------------------------- 1 | headers = stb_image.h 2 | compilerOpts = -DSTB_IMAGE_IMPLEMENTATION=1 -------------------------------------------------------------------------------- /src/resources/2/SimpleFragmentShader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Ouput data 4 | out vec3 color; 5 | 6 | void main() 7 | { 8 | // Output color = red 9 | color = vec3(1,0,0); 10 | } -------------------------------------------------------------------------------- /src/resources/2/SimpleVertexShader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 vertexPosition_modelspace; 5 | 6 | void main(){ 7 | gl_Position.xyz = vertexPosition_modelspace; 8 | gl_Position.w = 1.0; 9 | } -------------------------------------------------------------------------------- /src/resources/3/SimpleTransform.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 vertexPosition_modelspace; 5 | 6 | // Values that stay constant for the whole mesh. 7 | uniform mat4 MVP; 8 | 9 | void main(){ 10 | // Output position of the vertex, in clip space : MVP * position 11 | gl_Position = MVP * vec4(vertexPosition_modelspace,1); 12 | } -------------------------------------------------------------------------------- /src/resources/3/SingleColor.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Output data 4 | out vec3 color; 5 | 6 | void main() 7 | { 8 | // Output color = red 9 | color = vec3(1,0,0); 10 | } 11 | -------------------------------------------------------------------------------- /src/resources/5/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | in vec3 ourColor; 5 | in vec2 TexCoord; 6 | 7 | // texture samplers 8 | uniform sampler2D texture1; 9 | uniform sampler2D texture2; 10 | 11 | void main() 12 | { 13 | // linearly interpolate between both textures (80% container, 20% awesomeface) 14 | FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2); 15 | } -------------------------------------------------------------------------------- /src/resources/5/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/norman784/kotlin-native-opengl-tutorial/85f99043fd9b03ca060868432a897813e0139e5b/src/resources/5/logo.png -------------------------------------------------------------------------------- /src/resources/5/smiley.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/norman784/kotlin-native-opengl-tutorial/85f99043fd9b03ca060868432a897813e0139e5b/src/resources/5/smiley.png -------------------------------------------------------------------------------- /src/resources/5/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aColor; 4 | layout (location = 2) in vec2 aTexCoord; 5 | 6 | out vec3 ourColor; 7 | out vec2 TexCoord; 8 | 9 | void main() 10 | { 11 | gl_Position = vec4(aPos, 1.0); 12 | ourColor = aColor; 13 | TexCoord = aTexCoord; 14 | } --------------------------------------------------------------------------------