filter.frag
#version 300 es
precision highp float;
const float monoR = 0.298912;
const float monoG = 0.586611;
const float monoB = 0.114478;
const vec3 monochromeScale = vec3(monoR, monoG, monoB);
uniform sampler2D uTexture0;
uniform float uKernelX[9];
uniform float uKernelY[9];
uniform bool uUseFilter;
uniform bool uMonoChrome;
in vec2 vTextureCoords;
out vec4 fragColor;
void main() {
vec2 offset[9];
offset[0] = vec2(-1.0, -1.0);
offset[1] = vec2( 0.0, -1.0);
offset[2] = vec2( 1.0, -1.0);
offset[3] = vec2(-1.0, 0.0);
offset[4] = vec2( 0.0, 0.0);
offset[5] = vec2( 1.0, 0.0);
offset[6] = vec2(-1.0, 1.0);
offset[7] = vec2( 0.0, 1.0);
offset[8] = vec2( 1.0, 1.0);
ivec2 textureSize = textureSize(uTexture0, 0);
vec2 texelSize = 1.0 / vec2(float(textureSize.x), float(textureSize.y));
vec2 center = gl_FragCoord.xy;
vec3 horizonColor = vec3(0.0);
vec3 verticalColor = vec3(0.0);
vec4 finalColor = vec4(0.0);
if (uUseFilter) {
for (int i = 0; i < 9; i++) {
vec2 offsetX = (center + offset[i]) * texelSize.x;
vec2 offsetY = (center + offset[i]) * texelSize.y;
horizonColor += texture(uTexture0, offsetX).rgb * uKernelX[i];
verticalColor += texture(uTexture0, offsetY).rgb * uKernelY[i];
}
finalColor = vec4(sqrt(horizonColor * horizonColor + verticalColor * verticalColor), 1.0);
} else {
finalColor = texture(uTexture0, vTextureCoords);
}
if (uMonoChrome) {
float grayColor = dot(finalColor.rgb, monochromeScale);
finalColor = vec4(vec3(grayColor), 1.0);
}
fragColor = finalColor;
}
render.ts
import { Space } from "@/lib/canvas/index"
import { Program } from "@/lib/webgl/program"
import { Scene } from "@/lib/webgl/scene"
import { Camera } from "@/lib/webgl/camera"
import { Transforms } from "@/lib/webgl/transforms"
import { Matrix4 } from "@/lib/math/matrix"
import { torus } from "@/lib/shape/torus"
import { Vector3 } from "@/lib/math/vector"
import { Clock } from "@/lib/event/clock"
import { Light } from "@/lib/webgl/light"
import { hsvaToRgba } from "@/lib/shape/color"
import { Frame } from "@/lib/webgl/frame"
import { ControlUi } from "@/lib/gui/control-ui"
import { UniformLoader } from "@/lib/webgl/uniform-loader"
import mainVertSrc from "./index.vert?raw"
import mainFragSrc from "./index.frag?raw"
import filterVertSrc from "./filter.vert?raw"
import filterFragSrc from "./filter.frag?raw"
type FilterType = "Prewitt" | "Sobel" | "Roberts"
export const onload = () => {
const space = new Space("gl-canvas")
const canvas = space.canvas
const gl = space.gl
if (!canvas || !gl) return
let scene: Scene
let program: Program
let camera: Camera
let transforms: Transforms
let clock: Clock
let light: Light
let offscreen: Frame
let count = 0
let count2 = 0
const uniforms = new UniformLoader(gl, ["uKernelX", "uKernelY", "uUseFilter", "uMonoChrome"])
const filterTypes: FilterType[] = ["Prewitt", "Sobel", "Roberts"]
const kernels = {
Prewitt: {
x: [-1, 0, 1, -1, 0, 1, -1, 0, 1],
y: [-1, -1, -1, 0, 0, 0, 1, 1, 1]
},
Sobel: {
x: [-1, 0, 1, -2, 0, 2, -1, 0, 1],
y: [-1, -2, -1, 0, 0, 0, 1, 2, 1]
},
Roberts: {
x: [0, 0, 0, 0, 1, 0, 0, 0, -1],
y: [0, 0, 0, 0, 0, 1, 0, -1, 0]
}
}
let filterType: FilterType = "Prewitt"
let useFilter = true
let monochrome = false
const initGuiControls = () => {
const ui = new ControlUi()
ui.boolean("Edge detection", true, (isActive) => (useFilter = isActive))
ui.select<FilterType>("Operator", "Prewitt", filterTypes, (mode) => (filterType = mode))
ui.boolean("Monochrome", false, (isActive) => (monochrome = isActive))
}
const onResize = () => {
space.fitScreenSquare()
offscreen.resize()
render()
}
const configure = () => {
space.fitScreenSquare()
gl.enable(gl.DEPTH_TEST)
gl.depthFunc(gl.LEQUAL)
gl.enable(gl.CULL_FACE)
program = new Program(gl, mainVertSrc, mainFragSrc, false)
camera = new Camera()
camera.fov = 90
camera.near = 0.1
camera.far = 100
scene = new Scene(gl, program)
offscreen = new Frame(gl, canvas, filterVertSrc, filterFragSrc, 0)
transforms = new Transforms(gl, program, camera, canvas)
light = new Light(gl, program)
clock = new Clock()
uniforms.init(offscreen.program)
space.onResize = onResize
}
const registerGeometry = () => {
const torusGeometry = torus(2.0, 1.0, 64, 64, [1.0, 1.0, 1.0, 1.0])
scene.add(torusGeometry)
}
const render = () => {
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
count++
count % 2 === 0 && count2++
const rad = ((count % 360) * Math.PI) / 180
gl.bindFramebuffer(gl.FRAMEBUFFER, offscreen.frameBuffer)
const hsv = hsvaToRgba(count2 % 360, 1.0, 1.0, 1.0)
gl.clearColor(...hsv)
gl.clearDepth(1.0)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
program.use()
camera.eye = [0.0, 0.0, 0.0]
camera.position = [0.0, 20.0, 0.0]
camera.up = [0.0, 0.0, -1.0]
camera.update()
const torusCount = 9
scene.traverseDraw((obj) => {
obj.bind()
for (let i = 0; i < torusCount; i++) {
const model = Matrix4.identity()
.rotateY((i * 2 * Math.PI) / torusCount)
.translate(0.0, 0.0, 10.0)
.rotateAround(new Vector3(1.0, 1.0, 0.0).normalize(), rad)
light.ambientColor = hsvaToRgba(i * 40, 1, 1, 1)
light.direction = [-0.577, 0.577, 0.577]
light.model = model
light.eye = camera.position
light.reflect()
transforms.Model = model
transforms.setMatrixUniforms()
gl.drawElements(gl.TRIANGLES, obj.indices.length, gl.UNSIGNED_SHORT, 0)
}
obj.cleanup()
})
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clearDepth(1.0)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
offscreen.bind()
uniforms.fvector1("uKernelX", kernels[filterType].x)
uniforms.fvector1("uKernelY", kernels[filterType].y)
uniforms.boolean("uUseFilter", useFilter)
uniforms.boolean("uMonoChrome", monochrome)
offscreen.draw()
}
const init = () => {
configure()
registerGeometry()
clock.on("tick", render)
initGuiControls()
}
init()
}