# [图形学笔记系列] 实现一个简单的软光栅渲染效果-01

·  阅读 148

## 目标

canvas画布上渲染出如下所示的效果。 （素材都是源自：Tiny renderer or how OpenGL works: software rendering in 500 lines of code

## 实践部分

### 需要用到的工具

#### canvas像素点着色的工具函数

function createCanvas(targetDom, width, height) {
let dom_canvas = document.createElement('canvas')
let ctx = dom_canvas.getContext('2d')
let frameBuffer = ctx.getImageData(0, 0, width, height)
dom_canvas.width = width
dom_canvas.height = height
targetDom.appendChild(dom_canvas)

function drawPixel(x, y, color) {  //索引向下取整
let index = (Math.floor(x) + Math.floor(y) * width) * 4
for(let i = 0; i < 4; i++) {
frameBuffer.data[index + i] = color[i] * 255  //color值为0-1，约定，方便color参数的计算。
}
}

function draw() {     //像frameBuffer的数据渲染出来
ctx.putImageData(frameBuffer, 0, 0)
}

function clear() {   //清除画布
ctx.clearRect(0, 0, width, height)
frameBuffer = ctx.getImageData(0, 0, width, height)
}

return {drawPixel, draw, clear}
}

        let {drawPixel, draw, clear} = createCanvas(document.querySelector('body'), 600, 600)

for(let i = 0; i < 600; i++) {
drawPixel(i, i, [0.5,0.5,0.5,1])
}
draw()

#### 模型数据的解析

/*
* 偷个懒，顶点坐标、uv坐标以及法线坐标的数据我事先已经整理成数组了，面数组里存储每个面的三个顶点
* 及其对应的UV坐标和法线坐标，以索引的形式存储。
*/
function createModel(_vertices, _uvs, _normals, _faces) {
let length = _faces.length;
function getFace(index) {
let face = []
for(let i = 0; i < 3; i++) {
face.push(
{
vertice: _vertices[_faces[index-1][i][0]-1],  //原始数据里索引是从1开始的，所以这里都要减1
uv: _uvs[_faces[index-1][i][1]-1],
normal: _normals[_faces[index-1][i][2]-1]
}
)
}
return face
}
return { getFace, length }
}

        let {drawPixel, draw, clear} = createCanvas(document.querySelector('body'), 600, 600)
let model = createModel(verticles, uvs, normals, faces)

for(let i = 1; i <= model.length; i++) {
let face = model.getFace(i)
for(let j = 0; j < 3; j++) {
drawPixel((face[j].vertice[0]+1)*300, (-face[j].vertice[1]+1)*300, [0, 1, 0, 1])
//这里加了点视口变换
}
}
draw()

#### 纹理图片的读取

async function createTexture(url, width, height) {

let dom_canvas = document.createElement('canvas')
dom_canvas.width = width
dom_canvas.height = height
let ctx = dom_canvas.getContext('2d')

function getTexture(uv) {
let u = uv[0] * width         //uv坐标是从0-1，所以需要转换至像素坐标
let v = uv[1] * height
let color = []
let index = (Math.floor(u) + Math.floor(v) * width) * 4  //ImageData一维数组存储颜色，所以索引需要重新计算一下
for(let i = 0; i < 4; i++) {
color.push(img_data[index + i]/255)
}
return color
}

let img_data = await new Promise((resolve, reject) => { //用Promise包装一下纹理加载
let texture = new Image(width, height)
texture.src = url
ctx.drawImage(texture, 0, 0, width, height)
resolve(ctx.getImageData(0, 0, width, height).data)
}
texture.onerror = () => reject('Texture Error....')
})

return getTexture

}

        let {drawPixel, draw, clear} = createCanvas(document.querySelector('body'), 600, 600)
let model = createModel(verticles, uvs, normals, faces)
let texture = createTexture('./african_head_diffuse.jpg', 600, 600)
texture.then((getTexture)=>{
for(let i = 0; i < 600; i++) {
for(let j = 0; j < 600; j++) {
drawPixel(i, j, getTexture([i/600, j/600]))
}
}

draw()
})

## 参考资料

GAMES101-现代计算机图形学入门-闫令琪

Fundamentals of Computer Graphics, Fourth Edition

Tiny renderer or how OpenGL works: software rendering in 500 lines of code