## 码农干货系列【10】--光线追踪进阶:javascript玩转3D纹理映射

2013-03-18 07:59  【当耐特】  阅读(7149)  评论(11编辑  收藏  举报

# 简介

3D雕刻，顾名思义--在3D物体上进行雕刻，所以要达到的目的不仅仅是渲染几种常见的几何形状，还包括在几何形状上绘制、绘画等等。本文依旧使用大家熟悉的javascript语言，HTML5 canvas作为显示屏。

javascript基础知识

Vector3的几何意义(使用时候要区分什么时候代表点，什么时候代表向量)

# Vector3类

var Vector3 = function (x, y, z) { this.x = x; this.y = y; this.z = z; };
Vector3.prototype = {
dot: function (v) { return this.x * v.x + this.y * v.y + this.z * v.z; },
sub: function (v) { return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z); },
normalize: function () { return this.divideScalar(this.length()); },
divideScalar: function (s) { return new Vector3(this.x / s, this.y / s, this.z / s); },
length: function () { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); },
sqrLength: function () { return this.x * this.x + this.y * this.y + this.z * this.z; },
multiplyScalar: function (s) { return new Vector3(this.x * s, this.y * s, this.z * s); },
add: function (v) { return new Vector3(this.x + v.x, this.y + v.y, this.z + v.z); },
cross: function (v) { return new Vector3(-this.z * v.y + this.y * v.z, this.z * v.x - this.x * v.z, -this.y * v.x + this.x * v.y); },
round: function () { return new Vector3(Math.round(this.x), Math.round(this.y), Math.round(this.z)) },
distanceTo: function (v) {   return Math.sqrt(this.distanceToSquared(v)); },
distanceToSquared: function (v) {   var dx = this.x - v.x;   var dy = this.y - v.y; var dz = this.z - v.z;
return dx * dx + dy * dy + dz * dz;
}
}

var Vector4 = function ( x, y, z, w ) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
this.w = w || 1;
}

Vector4的最后一个参数w是干什么的？为什么不使用4*4矩阵？为什么是4*4？不是3*3?

##### “齐次坐标表示是计算机图形学的重要手段之一，它既能够用来明确区分向量和点，同时也更易用于进行仿射（线性）几何变换。”

—— F.S. Hill, JR

3*3的矩阵扩大成4*3的矩阵==>增加的那个维度可以用来进行w的计算

-----摘自《HTML5实验室:Canvas世界》

# 射线与正方体碰撞检测

var Cube = function (center, length) {
this.center = center;
this.length = length;
this.hLength = length / 2;

this.minX = this.center.x - this.hLength;
this.maxX = this.center.x + this.hLength;
this.minY = this.center.y - this.hLength;
this.maxY = this.center.y + this.hLength;
this.minZ = this.center.z - this.hLength;
this.maxZ = this.center.z + this.hLength;
}

Ray3 = function (origin, direction) {
this.origin = origin;
this.direction = direction;
}

Ray3.prototype = {
getPoint: function (t) {
}
}

Cube.prototype.intersect = function (r3) {
var d = r3.direction, p1 = r3.origin;
var irs = [];
var ir1 = this.getTIntersectPlane(p1, d, "z", this.center.z - this.hLength);
var ir2 = this.getTIntersectPlane(p1, d, "z", this.center.z + this.hLength);
var ir3 = this.getTIntersectPlane(p1, d, "x", this.center.x - this.hLength);
var ir4 = this.getTIntersectPlane(p1, d, "x", this.center.x + this.hLength);
var ir5 = this.getTIntersectPlane(p1, d, "y", this.center.y - this.hLength);
var ir6 = this.getTIntersectPlane(p1, d, "y", this.center.y + this.hLength);

if (ir1) irs.push(ir1);
if (ir2) irs.push(ir2);
if (ir3) irs.push(ir3);
if (ir4) irs.push(ir4);
if (ir5) irs.push(ir5);
if (ir6) irs.push(ir6);

if (irs.length === 1) {
return irs[0].cp;
}
else if (irs.length === 2) {
if (irs[0].t > irs[1].t) return irs[1].cp;
if (irs[1].t > irs[0].t) return irs[0].cp;
}

return null;
}

Cube.prototype.getTIntersectPlane = function (p1,d,type,value) {

var _intersectResult = [];
var t, cp;
if (type === "z")
{
t= (value - p1.z) / d.z;
if (cp.x < this.maxX && cp.x > this.minX && cp.y < this.maxY && cp.y > this.minY) return { t: t, cp: cp };
}
if (type === "x") {
t = (value - p1.x) / d.x;
if (cp.z < this.maxZ && cp.z > this.minZ && cp.y < this.maxY && cp.y > this.minY) return { t: t, cp: cp };
}
if (type === "y") {
t = (value - p1.y) / d.y;
if (cp.x < this.maxX && cp.x > this.minX && cp.z < this.maxZ && cp.z > this.minZ) return { t: t, cp: cp };
}

return null;
}

 if (irs[0].t > irs[1].t) return irs[1].cp;
if (irs[1].t > irs[0].t) return irs[0].cp;

# 绘制渐变文字

var linearText = ctx.createLinearGradient(0, 0, 200, 200);
ctx.fillStyle = linearText;
var fontSize = fontSize || "200px";
ctx.font = "bold 200px Arial";
ctx.textBaseline = "top";
ctx.fillText("当", 0, 0);

# 2D文字Mapping立方体表面

function createWordData(word) {
var canvas = document.createElement("canvas");
canvas.width = 90;
canvas.height = 90;
var ctx = canvas.getContext("2d");

var linearText = ctx.createLinearGradient(0, 0, 90, 90);
ctx.fillStyle = linearText;
ctx.font = "bold 70px Arial";
ctx.textBaseline = "top";
ctx.fillText(word, 20, 15);

var imgdata = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imgdata.data, tempN = 0;
var wordData = [];
for (var y = 0; y < canvas.height; y++) {
for (var x = 0; x < canvas.width; x++) {
if (pixels[tempN + 3] !== 0) {
wordData.push({ position: { x: x, y: y }, color: [pixels[tempN], pixels[tempN + 1], pixels[tempN + 2], pixels[tempN + 3]] })
}
tempN += 4;
}
}

return wordData;
}

p._testHit = function (ctx) {
try {
var hit = ctx.getImageData(0, 0, 1, 1).data[3] > 1;
} catch (e) {
throw "An error has occurred. This is most likely due to security restrictions on reading canvas pixel data with local or cross-domain images.";
}
return hit;
}

var color = null;
for (var k = 0, l = word1.length; k < l; k++) {
var dD = word1[k];
if (Math.round(result2.y) === cube.maxY && Math.round(result2.x - cube.minX) === dD.position.x && Math.round(result2.z - cube.minZ) === dD.position.y) {
color = dD.color;
break;

}
}
if (!color) {
for (var k = 0, l = word2.length; k < l; k++) {
var dD = word2[k];
if (Math.round(result2.z) === cube.maxZ && Math.round(result2.x - cube.minX) === dD.position.x && Math.round(cube.maxY - result2.y) === dD.position.y) {
color = dD.color;
break;

}
}
}
if (!color) {
for (var k = 0, l = word3.length; k < l; k++) {
var dD = word3[k];
if (Math.round(result2.x) === cube.maxX && Math.round(cube.maxY - result2.y) === dD.position.y && Math.round(cube.maxZ - result2.z) === dD.position.x) {
color = dD.color;
break;

}
}
}.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, "Courier New", courier, monospace; background-color: rgba(255, 255, 255, 1) }
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }

# 2D图片Mapping立方体表面

（要以data:image/png;base64,开头,别把整个img粘贴进textarea）
ps:这里还有一只base64企鹅http://1.iamzhanglei.sinaapp.com/base64.html

# 雕刻球体

1.在球体的正面放上一个垂直地面的正方形平面（可以理解为正方体的正面），

2.假定文字绘制到正方形平面，

3.正方形上的文字通过正交投影至球体表面。

function generateWordToBall(word1, word2, word3, ball, cube) {
var color = null;
var bca = [];
for (var k = 0, l = word1.length; k < l; k++) {
var dD = word1[k];
var r3 = new Ray3(new Vector3(cube.minX + dD.position.x - 20, cube.maxY - dD.position.y + 30, cube.maxZ), new Vector3(0, 0, -1));
var result3 = ball.intersect(r3);
if (result3)
bca.push({ cp: result3.round(), cl: dD.color });
}

for (var k = 0, l = word2.length; k < l; k++) {
var dD = word2[k];
var r3 = new Ray3(new Vector3(cube.minX + dD.position.x - 60, cube.maxY - dD.position.y, cube.maxZ), new Vector3(0, 0, -1));
var result3 = ball.intersect(r3);
if (result3)
bca.push({ cp: result3.round(), cl: dD.color });
}

for (var k = 0, l = word3.length; k < l; k++) {
var dD = word3[k];
var r3 = new Ray3(new Vector3(cube.minX + dD.position.x, cube.maxY - dD.position.y - 20, cube.maxZ), new Vector3(0, 0, -1));
var result3 = ball.intersect(r3);
if (result3)
bca.push({ cp: result3.round(), cl: dD.color });
}

return bca;
}

# 在线演示

that's all.Have fun!