diff --git a/src/jsMain/kotlin/LightSource.kt b/src/jsMain/kotlin/LightSource.kt new file mode 100644 index 0000000..7ba920b --- /dev/null +++ b/src/jsMain/kotlin/LightSource.kt @@ -0,0 +1,5 @@ +class LightSource( + var pos: Array, + var ambientColor: Array, + var diffuseColor: Array, + var specularColor: Array) \ No newline at end of file diff --git a/src/jsMain/kotlin/Main.kt b/src/jsMain/kotlin/Main.kt index 248c0b6..720cd82 100644 --- a/src/jsMain/kotlin/Main.kt +++ b/src/jsMain/kotlin/Main.kt @@ -1,87 +1,53 @@ -import com.tanelso2.glmatrix.Mat3 -import com.tanelso2.glmatrix.Mat4 -import com.tanelso2.glmatrix.Vec3 -import org.khronos.webgl.Float32Array -import org.khronos.webgl.WebGLProgram -import org.khronos.webgl.WebGLShader import org.w3c.dom.HTMLCanvasElement import kotlinx.browser.document import kotlinx.browser.window -import kotlin.math.PI import org.khronos.webgl.WebGLRenderingContext as GL -class WebGLWrapper { - val canvas: HTMLCanvasElement = document.getElementById("webglCanvas") as HTMLCanvasElement - val webgl: GL = canvas.getContext("webgl") as GL - val shaderProgram: WebGLProgram = webgl.createProgram() ?: throw IllegalStateException("Could not initialize shader program") +class Main { + private val canvas: HTMLCanvasElement = document.getElementById("webglCanvas") as HTMLCanvasElement + private val webgl: GL = canvas.getContext("webgl") as GL + private val shaderProgram: ShaderProgram = ShaderProgram(webgl) - val scaleFactor by HTMLInputProperty("scaleInput") + private val scaleFactor by HTMLInputProperty("scaleInput") - val lightPos: Array by ArrayOfInputsProperty( + private val lightPos: Array by ArrayOfInputsProperty( "lightPosX", "lightPosY", "lightPosZ" ) - val shininess by HTMLInputProperty("shininessInput") + private val shininess by HTMLInputProperty("shininessInput") - val rotationSpeed by HTMLInputProperty("rotationSpeedInput") - - init { - webgl.enable(GL.DEPTH_TEST) - } - - val windowWidth = 800 - val windowHeight = 600 + private val rotationSpeed by HTMLInputProperty("rotationSpeedInput") private val vertexShaderLocation = "vertex-shader.glsl" private val fragmentShaderLocation = "frag-shader.glsl" private val objFileLocation = "teapot.obj" - val resourceList = arrayOf( + private val resourceList = arrayOf( fragmentShaderLocation, vertexShaderLocation, objFileLocation ) - val resourceLoader = ResourceLoader(*resourceList) - val objFileLoader: ObjLoader by lazy { ObjLoader(resourceLoader[objFileLocation]!!) } + private val resourceLoader = ResourceLoader(*resourceList) + private val teapot: TeapotObj by lazy { TeapotObj(webgl, resourceLoader[objFileLocation]!!, shaderProgram)} + private val light: LightSource = LightSource( + lightPos, + arrayOf(0.1f, 0.1f, 0.1f), + arrayOf(0.4f, 0.4f, 0.0f), + arrayOf(1.0f, 1.0f, 1.0f) + ) + + init { + webgl.enable(GL.DEPTH_TEST) + } fun setup() { - if(resourceLoader.allLoaded()) { - compileShaderProgram() - - setupAttribute("aVertexPosition", objFileLoader.getVertices()) - setupAttribute("aVertexNormal", objFileLoader.getVertexNormals()) - - println("Vertex array size = ${objFileLoader.getVertices().length}") - println("Vertex normal array size = ${objFileLoader.getVertexNormals().length}") - - - val ambientColor = arrayOf( - 0.1f, - 0.1f, - 0.1f - ) - val diffuseColor = arrayOf( - 0.4f, - 0.4f, - 0.0f - ) - val specularColor = arrayOf( - 1.0f, - 1.0f, - 1.0f - ) - - setupUniformVec3Float(lightPos, "uLightPos") - setupUniformVec3Float(ambientColor, "uAmbientColor") - setupUniformVec3Float(diffuseColor, "uDiffuseColor") - setupUniformVec3Float(specularColor, "uSpecularColor") - - val faceIndexBuffer = webgl.createBuffer() - webgl.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, faceIndexBuffer) - webgl.bufferData(GL.ELEMENT_ARRAY_BUFFER, objFileLoader.getFaces(), GL.STATIC_DRAW) + if (resourceLoader.allLoaded()) { + val vertexShaderSource = resourceLoader[vertexShaderLocation]!! + val fragmentShaderSource = resourceLoader[fragmentShaderLocation]!! + shaderProgram.compile(vertexShaderSource, fragmentShaderSource) window.requestAnimationFrame { render() } } else { @@ -90,81 +56,15 @@ class WebGLWrapper { } } - private fun setupUniformVec3Float(lightPos: Array, name: String) { - val lightPositionLoc = webgl.getUniformLocation(shaderProgram, name) - webgl.uniform3fv(lightPositionLoc, lightPos) - } - - private fun setupAttribute(attributeName: String, attributeArray: Float32Array) { - val vertexPositionAttribute = webgl.getAttribLocation(shaderProgram, attributeName) - webgl.enableVertexAttribArray(vertexPositionAttribute) - val vertexPositionBuffer = webgl.createBuffer() - webgl.bindBuffer(GL.ARRAY_BUFFER, vertexPositionBuffer) - webgl.bufferData(GL.ARRAY_BUFFER, attributeArray, GL.STATIC_DRAW) - webgl.vertexAttribPointer(vertexPositionAttribute, 3, GL.FLOAT, false, 0, 0) - } - - private fun compileShaderProgram() { - val vertexShaderSource = resourceLoader[vertexShaderLocation]!! - val vertexShader = getVertexShader(vertexShaderSource) - val fragmentShaderSource = resourceLoader[fragmentShaderLocation]!! - val fragmentShader = getFragmentShader(fragmentShaderSource) - webgl.attachShader(shaderProgram, vertexShader) - webgl.attachShader(shaderProgram, fragmentShader) - webgl.linkProgram(shaderProgram) - println(webgl.getProgramInfoLog(shaderProgram)) - webgl.useProgram(shaderProgram) - } - - fun getFragmentShader(source: String): WebGLShader = getShader(source, GL.FRAGMENT_SHADER) - fun getVertexShader(source: String): WebGLShader = getShader(source, GL.VERTEX_SHADER) - - - private fun getShader(shaderSource: String, shaderType: Int): WebGLShader { - val shader = webgl.createShader(shaderType) - webgl.shaderSource(shader, shaderSource) - webgl.compileShader(shader) - if(!(webgl.getShaderParameter(shader, GL.COMPILE_STATUS) as Boolean)) { - println(webgl.getShaderInfoLog(shader)) - } - return shader ?: throw IllegalStateException("Shader is null!") - } var rotation = 0.0 fun draw() { - setupUniformVec3Float(lightPos, "uLightPos") - - val shininessUniform = webgl.getUniformLocation(shaderProgram, "shininess") - webgl.uniform1f(shininessUniform, shininess.toFloat()) - - val pMatrix = Mat4.perspective(PI / 3, 16.0 / 9.0, 0.1, 60.0) - - val vMatrix = Mat4.lookAt(Vec3(20,20,20), Vec3(0,0,0), Vec3(0,0,1)) - - val mMatrix = Mat4() - mMatrix.scale(scaleFactor) + //setupAttributes() + light.pos = lightPos rotation += rotationSpeed - mMatrix.rotateX(PI / 2) - mMatrix.rotateY(rotation) - - val nMatrix = Mat3.fromMat4(vMatrix * mMatrix) - nMatrix.transpose() - nMatrix.invert() - - val nMatrixUniform = webgl.getUniformLocation(shaderProgram, "uNMatrix") - webgl.uniformMatrix3fv(nMatrixUniform, false, nMatrix.array) - - val pMatrixUniform = webgl.getUniformLocation(shaderProgram, "uPMatrix") - webgl.uniformMatrix4fv(pMatrixUniform, false, pMatrix.array) - - val vMatrixUniform = webgl.getUniformLocation(shaderProgram, "uVMatrix") - webgl.uniformMatrix4fv(vMatrixUniform, false, vMatrix.array) - - val mMatrixUniform = webgl.getUniformLocation(shaderProgram, "uMMatrix") - webgl.uniformMatrix4fv(mMatrixUniform, false, mMatrix.array) - webgl.drawElements(GL.TRIANGLES, objFileLoader.getNumFaces() * 3, GL.UNSIGNED_SHORT, 0) + teapot.draw(scaleFactor, rotation, shininess, light) } fun render() { @@ -177,7 +77,7 @@ class WebGLWrapper { fun main() { document.body?.onload = { - val wrapper = WebGLWrapper() + val wrapper = Main() window.requestAnimationFrame { wrapper.setup() } } } diff --git a/src/jsMain/kotlin/ShaderProgram.kt b/src/jsMain/kotlin/ShaderProgram.kt new file mode 100644 index 0000000..14f4f85 --- /dev/null +++ b/src/jsMain/kotlin/ShaderProgram.kt @@ -0,0 +1,53 @@ +import com.tanelso2.glmatrix.Mat3 +import com.tanelso2.glmatrix.Mat4 +import org.khronos.webgl.Float32Array +import org.khronos.webgl.WebGLProgram +import org.khronos.webgl.WebGLRenderingContext +import org.khronos.webgl.WebGLUniformLocation + +class ShaderProgram(private val webgl: WebGLRenderingContext) { + val program: WebGLProgram = + webgl.createProgram() ?: throw IllegalStateException("Failed to create shader program") + + fun useProgram() { + webgl.useProgram(program) + } + + fun compile(vertexShaderSource: String, fragmentShaderSource: String) { + val vertexShader = webgl.getVertexShader(vertexShaderSource) + val fragmentShader = webgl.getFragmentShader(fragmentShaderSource) + webgl.attachShader(program, vertexShader) + webgl.attachShader(program, fragmentShader) + webgl.linkProgram(program) + useProgram() + } + + fun setupAttribute(name: String, array: Float32Array) { + val attribute = webgl.getAttribLocation(program, name) + webgl.enableVertexAttribArray(attribute) + val buffer = webgl.createBuffer() + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, buffer) + webgl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, array, WebGLRenderingContext.STATIC_DRAW) + webgl.vertexAttribPointer(attribute, 3, WebGLRenderingContext.FLOAT, false, 0, 0) + } + + fun getUniformLocation(name: String) : WebGLUniformLocation = + webgl.getUniformLocation(program, name) + ?: throw IllegalArgumentException("Could not find uniform named '$name'") + + fun setupUniformMat4(name: String, value: Mat4, transpose: Boolean = false) { + webgl.uniformMatrix4fv(getUniformLocation(name), transpose, value.array) + } + + fun setupUniformMat3(name: String, value: Mat3, transpose: Boolean = false) { + webgl.uniformMatrix3fv(getUniformLocation(name), transpose, value.array) + } + + fun setupUniformFloat(name: String, value: Float) { + webgl.uniform1f(getUniformLocation(name), value) + } + + fun setupUniformVec3Float(name: String, value: Array) { + webgl.uniform3fv(getUniformLocation(name), value) + } +} \ No newline at end of file diff --git a/src/jsMain/kotlin/TeapotObj.kt b/src/jsMain/kotlin/TeapotObj.kt new file mode 100644 index 0000000..0fbcd01 --- /dev/null +++ b/src/jsMain/kotlin/TeapotObj.kt @@ -0,0 +1,70 @@ +import com.tanelso2.glmatrix.Mat3 +import com.tanelso2.glmatrix.Mat4 +import com.tanelso2.glmatrix.Vec3 +import org.khronos.webgl.WebGLRenderingContext +import kotlin.math.PI + +class TeapotObj(private val webgl: WebGLRenderingContext, private val objFileSource: String, private val shaderProgram: ShaderProgram) { + private val objLoader = ObjLoader(objFileSource) + private val faces = objLoader.getFaces() + private val vertices = objLoader.getVertices() + private val vertexNormals = objLoader.getVertexNormals() + private val numFaces = objLoader.getNumFaces() + + private val faceIndexBuffer = webgl.createBuffer() + private val pMatrix = Mat4.perspective(PI / 3, 16.0 / 9.0, 0.1, 60.0) + private val vMatrix = Mat4.lookAt(Vec3(20, 20, 20), Vec3(0, 0, 0), Vec3(0, 0, 1)) + + private fun setupFaceBuffer() { + webgl.bindBuffer(WebGLRenderingContext.ELEMENT_ARRAY_BUFFER, faceIndexBuffer) + webgl.bufferData(WebGLRenderingContext.ELEMENT_ARRAY_BUFFER, faces, WebGLRenderingContext.STATIC_DRAW) + } + + private fun setupAttributes() { + shaderProgram.apply { + setupAttribute("aVertexPosition", vertices) + setupAttribute("aVertexNormal", vertexNormals) + } + } + + private fun setupMatrices(scaleFactor: Double, rotation: Double) { + val mMatrix = Mat4() + mMatrix.scale(scaleFactor) + mMatrix.rotateX(PI / 2) + mMatrix.rotateY(rotation) + + val nMatrix = Mat3.fromMat4(vMatrix * mMatrix) + nMatrix.transpose() + nMatrix.invert() + + shaderProgram.apply { + setupUniformMat3("uNMatrix", nMatrix) + setupUniformMat4("uPMatrix", pMatrix) + setupUniformMat4("uVMatrix", vMatrix) + setupUniformMat4("uMMatrix", mMatrix) + } + } + + private fun setupLight(light: LightSource) { + shaderProgram.apply { + setupUniformVec3Float("uLightPos", light.pos) + setupUniformVec3Float("uAmbientColor", light.ambientColor) + setupUniformVec3Float("uDiffuseColor", light.diffuseColor) + setupUniformVec3Float("uSpecularColor", light.specularColor) + } + } + + fun draw(scaleFactor: Double, rotation: Double, shininess: Double, light: LightSource) { + shaderProgram.useProgram() + setupAttributes() + setupFaceBuffer() + setupMatrices(scaleFactor, rotation) + setupShininess(shininess) + setupLight(light) + webgl.drawElements(WebGLRenderingContext.TRIANGLES, numFaces * 3, WebGLRenderingContext.UNSIGNED_SHORT, 0) + } + + private fun setupShininess(shininess: Double) { + shaderProgram.setupUniformFloat("shininess", shininess.toFloat()) + } +} \ No newline at end of file diff --git a/src/jsMain/kotlin/WebGLHelpers.kt b/src/jsMain/kotlin/WebGLHelpers.kt new file mode 100644 index 0000000..959dfe8 --- /dev/null +++ b/src/jsMain/kotlin/WebGLHelpers.kt @@ -0,0 +1,15 @@ +import org.khronos.webgl.WebGLRenderingContext as GL +import org.khronos.webgl.WebGLShader + +fun GL.getShader(source: String, shaderType: Int): WebGLShader { + val shader = this.createShader(shaderType) + this.shaderSource(shader, source) + this.compileShader(shader) + if(!(this.getShaderParameter(shader, GL.COMPILE_STATUS) as Boolean)) { + println(this.getShaderInfoLog(shader)) + } + return shader ?: throw IllegalStateException("Shader is null!") +} + +fun GL.getFragmentShader(source: String): WebGLShader = this.getShader(source, GL.FRAGMENT_SHADER) +fun GL.getVertexShader(source: String): WebGLShader = this.getShader(source, GL.VERTEX_SHADER)