From 1e6314fcdd4d38df2134110fef9474f6f66b5f60 Mon Sep 17 00:00:00 2001 From: monoid Date: Sun, 10 Oct 2021 02:07:34 +0900 Subject: [PATCH] add 3d drawer --- package.json | 1 + src/app.ts | 44 +++++++++++++++++++++++++---- src/camera.ts | 32 +++++++++++++++++----- src/drawer3D.ts | 60 ++++++++++++++++++++++++++++++++++++++++ src/fragment.frag | 9 +++--- src/model.ts | 70 +++++++++++++++++++++++++++++++++++++++++++++++ src/vertex.vert | 14 ++++++++-- 7 files changed, 212 insertions(+), 18 deletions(-) create mode 100644 src/drawer3D.ts create mode 100644 src/model.ts diff --git a/package.json b/package.json index 4838666..3a4b514 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ ] }, "devDependencies": { + "@parcel/transformer-glsl": "^2.0.0-rc.0", "@types/react": "^17.0.27", "parcel": "^2.0.0-rc.0", "webgl-strict-types": "^1.0.5" diff --git a/src/app.ts b/src/app.ts index 3a8a10f..c2911e3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,18 +1,53 @@ import { Drawer2D } from "./drawer2D"; +import { Drawer3D } from "./drawer3D"; import { TriangleDrawer } from "./triangle_drawer"; export class CanvasApp{ readonly gl: WebGL2RenderingContext; renderer: Drawer2D; trenderer : TriangleDrawer; + r : Drawer3D; constructor(gl: WebGL2RenderingContext){ this.gl = gl; this.renderer = new Drawer2D(gl); this.trenderer = new TriangleDrawer(gl); + this.r = new Drawer3D(gl); } intialize():boolean{ this.renderer.prepare(); this.trenderer.prepare(this.gl); + this.r.init(); + + document.addEventListener("keydown",(e)=>{ + if(e.key == "h"){ + this.r.camera.rotateRight(Math.PI * 5 / 180.0); + } + if(e.key == "f"){ + this.r.camera.rotateRight(-Math.PI * 5 / 180.0) + } + if(e.key == "t"){ + this.r.camera.rotateUp(Math.PI * 5 / 180.0) + } + if(e.key == "g"){ + this.r.camera.rotateUp(-Math.PI * 5 / 180.0) + } + if(e.key == "d"){ + this.r.camera.pos[0] += 0.5; + } + if(e.key == "a"){ + this.r.camera.pos[0] -= 0.5; + } + if(e.key == "w"){ + this.r.camera.pos[2] += 0.5; + } + if(e.key == "s"){ + this.r.camera.pos[2] -= 0.5; + } + }); + + this.gl.clearColor(0,0,0,0); + this.gl.clearDepth(1); + this.gl.enable(this.gl.DEPTH_TEST); return true; } startRun(){ @@ -32,11 +67,10 @@ export class CanvasApp{ } drawScene(){ this.gl.viewport(0,0,this.gl.canvas.width,this.gl.canvas.height); - - this.gl.clearColor(0,0,0,0); - this.gl.clear(this.gl.COLOR_BUFFER_BIT); - this.renderer.draw(this.gl,{}); - this.trenderer.draw(this.gl,{}); + this.gl.clear(this.gl.COLOR_BUFFER_BIT|this.gl.DEPTH_BUFFER_BIT); + //this.renderer.draw(this.gl,{}); + //this.trenderer.draw(this.gl,{}); + this.r.draw(this.gl,{}); requestAnimationFrame(this.drawScene.bind(this)); } }; \ No newline at end of file diff --git a/src/camera.ts b/src/camera.ts index 4eea6cb..53a6143 100644 --- a/src/camera.ts +++ b/src/camera.ts @@ -3,12 +3,20 @@ import {mat4, vec3, quat, mat3} from "gl-matrix"; export class Camera{ pos:vec3; rot:quat; + #proj : mat4; + fovY : number; + aspect : number; + far : number; + near: number; constructor(pos:vec3,rot?:quat){ this.pos = pos; + this.#proj = mat4.create(); if(rot == undefined){ - const lookAt = mat4.lookAt(mat4.create(),this.pos,[0,0,0],[0,0,1]) + const lookAt = mat4.lookAt(mat4.create(),this.pos,[0,0,0],[0,1,0]); const rotMat = mat3.fromMat4(mat3.create(),lookAt); this.rot = quat.fromMat3(quat.create(), rotMat); + //quat.normalize(this.rot,this.rot); + //quat.conjugate(this.rot,this.rot); } else{ this.rot = rot; @@ -18,14 +26,24 @@ export class Camera{ quat.rotateY(this.rot,this.rot,rad); } rotateUp(rad:number){ - quat.rotateX(this.rot,this.rot,rad); - } - rotateClockwise(rad:number){ quat.rotateZ(this.rot,this.rot,rad); } - getViewMat(){ + rotateClockwise(rad:number){ + quat.rotateX(this.rot,this.rot,rad); + } + get viewMatrix():mat4{ + const p = vec3.negate(vec3.create() ,this.pos); + const t = mat4.fromTranslation(mat4.create(),p); const r = mat4.fromQuat(mat4.create(),this.rot); - mat4.translate(r,r,this.pos); - return r; + return mat4.mul(mat4.create(),r,t); + } + /** + * update projection matrix + */ + UpdateProjectionMat(){ + mat4.perspective(this.#proj,this.fovY,this.aspect,this.near,this.far); + } + get projectionMatrix():mat4{ + return this.#proj; } }; \ No newline at end of file diff --git a/src/drawer3D.ts b/src/drawer3D.ts new file mode 100644 index 0000000..ea0a487 --- /dev/null +++ b/src/drawer3D.ts @@ -0,0 +1,60 @@ + + +/// +import vert_src from "./vertex.vert"; +import frag_src from "./fragment.frag"; + +import {createProgramFromSource, ProgramError, ShaderError} from "./gl_util"; +import { Camera } from "./camera"; +import * as G from "./glWrapper"; +import { Drawable, RenderState } from "./drawable"; +import { Model } from "./model"; +import { mat4 } from "gl-matrix"; + + +export class Drawer3D implements Drawable{ + gl : WebGL2RenderingContext; + program: G.GLProgram; + camera : Camera; + model: Model | undefined; + constructor(gl: WebGL2RenderingContext){ + this.gl = gl; + this.camera = new Camera([-20,1,-10]); + this.camera.far = 1000; + this.camera.near = 0.1; + this.camera.fovY = Math.PI * 90 / 180; + this.camera.aspect = 1; + this.model = undefined; + try{ + this.program = new G.GLProgram(createProgramFromSource(gl,vert_src,frag_src)); + const attr = this.program.getActiveAttributes(gl); + console.log(attr); + } + catch(e){ + if(e instanceof ShaderError){ + console.log(e.info); + } + throw e; + } + } + async init(){ + const gl = this.gl; + const url = new URL("../assets/models/teapot/teapot.obj",import.meta.url); + this.model = await Model.loadFromOBJ(gl,url.href); + console.log("loading model complete"); + this.model.ready(gl,this.program); + } + draw(gl: WebGL2RenderingContext,state:RenderState): void { + if(this.model !== undefined){ + this.camera.aspect = gl.canvas.width/gl.canvas.height; + this.camera.UpdateProjectionMat(); + this.program.use(gl); + + this.program.setUniformMat4f(gl,"viewMat",this.camera.viewMatrix); + this.program.setUniformMat4f(gl,"projectionMat",this.camera.projectionMatrix); + this.program.setUniformMat4f(gl,"modelMat",mat4.fromTranslation(mat4.create(),[0,0,0])); + + this.model.draw(gl); + } + } +} \ No newline at end of file diff --git a/src/fragment.frag b/src/fragment.frag index 67ca4a9..5751df1 100644 --- a/src/fragment.frag +++ b/src/fragment.frag @@ -1,10 +1,11 @@ #version 300 es precision highp float; layout(location=0) out vec4 outColor; -//in vec4 gl_FragCoord; -//in vec2 gl_PointCoord; -uniform vec4 u_color; +in vec3 fragNormal; void main() { - outColor = u_color; + vec3 c = normalize(vec3(20,20,1)); + float intense = dot(c,normalize(fragNormal)); + intense = (max(intense,-0.5)+1.0)/2.0; + outColor = vec4(vec3(1,1,0) * intense,1); } \ No newline at end of file diff --git a/src/model.ts b/src/model.ts new file mode 100644 index 0000000..c94a8e8 --- /dev/null +++ b/src/model.ts @@ -0,0 +1,70 @@ +import { createIndexBuffer, createVertexArray, createVertexBuffer, GLProgram, IndexBuffer, VertexArray, VertexBuffer } from "./glWrapper"; +import {OBJ} from "webgl-obj-loader"; +import { assertBoolean } from "./util"; + +type VertexType = "position" | "normal" | "textureUV"; + +type Vertexes = {[key in VertexType]:VertexBuffer}; + +export class Model{ + vertexes : Vertexes; + ibo :IndexBuffer; + vao :VertexArray; + constructor(gl:WebGL2RenderingContext,vertex:Vertexes,index :IndexBuffer){ + this.vertexes = vertex; + this.vao = createVertexArray(gl); + this.ibo = index; + } + ready(gl:WebGL2RenderingContext,p : GLProgram){ + const VertexLayouts = { + "position":{ + count:3, + type:gl.FLOAT, + normalize:false, + stride:0, + offset:0 + }, + "normal":{ + count:3, + type:gl.FLOAT, + normalize:false, + stride:0, + offset:0 + }, + "textureUV":{ + count:2, + type:gl.FLOAT, + normalize:false, + stride:0, + offset:0 + } + } + this.vao.bind(gl); + for(const [name,buf] of Object.entries(this.vertexes)){ + const loc = p.getAttribLocation(gl,name); + assertBoolean(loc >= 0,"there is no",name,"attribute"); + this.vao.addBuffer(gl,buf,loc,VertexLayouts[name]); + } + this.ibo.bind(gl); + this.vao.unbind(gl); + } + draw(gl:WebGL2RenderingContext){ + this.vao.bind(gl); + gl.drawElements(gl.TRIANGLES,this.ibo.count,gl.UNSIGNED_SHORT,0); + this.vao.unbind(gl); + } + static async loadFromOBJ(gl:WebGL2RenderingContext,src:RequestInfo,init?:RequestInit){ + const response = await fetch(src,init); + const text = await response.text(); + const mesh = new OBJ.Mesh(text); + const position = createVertexBuffer(gl,mesh.vertices); + const normal = createVertexBuffer(gl,mesh.vertexNormals); + const texture = createVertexBuffer(gl,mesh.textures); + const index = createIndexBuffer(gl,mesh.indices); + return new Model(gl,{ + position: position, + normal: normal, + textureUV: texture + }, index); + } +} diff --git a/src/vertex.vert b/src/vertex.vert index 10a56d8..64d4a0b 100644 --- a/src/vertex.vert +++ b/src/vertex.vert @@ -1,6 +1,16 @@ #version 300 es -layout(location=0) in vec4 pos; + +layout(location=0) in vec3 position; +layout(location=1) in vec2 textureUV; +layout(location=2) in vec3 normal; + +uniform mat4 modelMat; +uniform mat4 viewMat; +uniform mat4 projectionMat; + +out vec3 fragNormal; void main() { - gl_Position = pos; + gl_Position = projectionMat * viewMat * modelMat * vec4(position,1); + fragNormal = mat3(transpose(inverse(modelMat))) * normal; } \ No newline at end of file