纹理逻辑
在 Html5 和 TypeScript/JavaScript 中,我们当然要通过动态创建一个 Canvas 元素加载材质并得到它相关的图像数据,以用来获得我们的颜色字节数组。
而在 C#/XAML 中,我们要创建一个 WriteableBitmap,设置源并在加载完图像后获得其缓冲区属性以此获取我们的颜色字节数组。
【译者注:C#代码】
public class Texture
{
private byte[] internalBuffer;
private int width;
private int height;
// 材质尺寸需要是 2 的次方(如:512x512、1024x1024 等)
public Texture(string filename, int width, int height)
{
this.width = width;
this.height = height;
Load(filename);
}
async void Load(string filename)
{
var file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(filename);
using (var stream = await file.OpenReadAsync())
{
var bmp = new WriteableBitmap(width, height);
bmp.SetSource(stream);
internalBuffer = bmp.PixelBuffer.ToArray();
}
}
// 获得 Blender 导出的 UV 坐标并将其对应的像素颜色返回
public Color4 Map(float tu, float tv)
{
// 图像尚未加载
if (internalBuffer == null)
{
return Color4.White;
}
// 使用%运算符来循环/重复需要的这个纹理
int u = Math.Abs((int) (tu*width) % width);
int v = Math.Abs((int) (tv*height) % height);
int pos = (u + v * width) * 4;
byte b = internalBuffer[pos];
byte g = internalBuffer[pos + 1];
byte r = internalBuffer[pos + 2];
byte a = internalBuffer[pos + 3];
return new Color4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
}
}
【译者注:TypeScript 代码】
export class Texture {
width: number;
height: number;
internalBuffer: ImageData;
// 材质尺寸需要是 2 的次方(如:512x512、1024x1024 等)
constructor(filename: string, width: number, height: number) {
this.width = width;
this.height = height;
this.load(filename);
}
public load(filename: string): void {
var imageTexture = new Image();
imageTexture.height = this.height;
imageTexture.width = this.width;
imageTexture.onload = () => {
var internalCanvas: HTMLCanvasElement = document.createElement("canvas");
internalCanvas.width = this.width;
internalCanvas.height = this.height;
var internalContext: CanvasRenderingContext2D = internalCanvas.getContext("2d");
internalContext.drawImage(imageTexture, 0, 0);
this.internalBuffer = internalContext.getImageData(0, 0, this.width, this.height);
};
imageTexture.src = filename;
}
// 获得 Blender 导出的 UV 坐标并将其对应的像素颜色返回
public map(tu: number, tv: number): BABYLON.Color4 {
if (this.internalBuffer) {
// 使用%运算符来循环/重复需要的这个纹理
var u = Math.abs(((tu * this.width) % this.width)) >> 0;
var v = Math.abs(((tv * this.height) % this.height)) >> 0;
var pos = (u + v * this.width) * 4;
var r = this.internalBuffer.data[pos];
var g = this.internalBuffer.data[pos + 1];
var b = this.internalBuffer.data[pos + 2];
var a = this.internalBuffer.data[pos + 3];
return new BABYLON.Color4(r / 255.0, g / 255.0, b / 255.0, a / 255.0);
}
// 图像尚未加载
else {
return new BABYLON.Color4(1, 1, 1, 1);
}
}
}
【译者注:JavaScript 代码】
var Texture = (function () {
// 材质尺寸需要是 2 的次方(如:512x512、1024x1024 等)
function Texture(filename, width, height) {
this.width = width;
this.height = height;
this.load(filename);
}
Texture.prototype.load = function (filename) {
var _this = this;
var imageTexture = new Image();
imageTexture.height = this.height;
imageTexture.width = this.width;
imageTexture.onload = function () {
var internalCanvas = document.createElement("canvas");
internalCanvas.width = _this.width;
internalCanvas.height = _this.height;
var internalContext = internalCanvas.getContext("2d");
internalContext.drawImage(imageTexture, 0, 0);
_this.internalBuffer = internalContext.getImageData(0, 0, _this.width, _this.height);
};
imageTexture.src = filename;
};
// 获得 Blender 导出的 UV 坐标并将其对应的像素颜色返回
Texture.prototype.map = function (tu, tv) {
if (this.internalBuffer) {
// 使用%运算符来循环/重复需要的这个纹理
var u = Math.abs(((tu * this.width) % this.width)) >> 0;
var v = Math.abs(((tv * this.height) % this.height)) >> 0;
var pos = (u + v * this.width) * 4;
var r = this.internalBuffer.data[pos];
var g = this.internalBuffer.data[pos + 1];
var b = this.internalBuffer.data[pos + 2];
var a = this.internalBuffer.data[pos + 3];
return new BABYLON.Color4(r / 255.0, g / 255.0, b / 255.0, a / 255.0);
}
// 图像尚未加载
else {
return new BABYLON.Color4(1, 1, 1, 1);
}
};
return Texture;
})();
SoftEngine.Texture = Texture;
传递纹理信息流程
我不会深入到每一个细节,下面有完整的代码下载,让我们来看看你都需要做些什么:
添加一个纹理属性到 Mesh 类和一个 Vector2 属性名称为 TextureCoordinates 的 Vertex 结构
更新 ScanLineData 中嵌入 8 个单精度小数/数字:每个顶点的 UV 坐标(ua, ub, uc, ud 和 va, vb, vc, vd)。
更新 Project 方法/函数返回一个新的 Vertex 和 TextureCoordinates 原封不动的使用(传递)
传递一个 Texture 对象作为 DrawTriangle 方法/函数的最后一个参数到 ProcessScanLine
- 填充新的 ScanLineData 结构在 drawTriangle 和相应的 UV 坐标
- 把 UV 插值到 ProcessScanLine 函数的 Y 上得到 SU/SV 和 EU/EV(start U/start V end U/End V)然后插值 U,V 在 X 上,就可以找到它在纹理中对应的颜色。将此颜色与对象本身的颜色(在本教程中一般它是白色)还有法线进行 NDotL 操作得到光量并进行混合。
注 :我们的 Project 方法可以被看作是我们命名为“Vertex Shader”的 3D 硬件引擎,并且 ProcessScanLine 可以被看作是"Pixel Shader"引擎。
这篇文章仅在 ProcessScanLine 中有新的更新部分:
【译者注:C#代码】
void ProcessScanLine(ScanLineData data, Vertex va, Vertex vb, Vertex vc, Vertex vd, Color4 color, Texture texture)
{
Vector3 pa = va.Coordinates;
Vector3 pb = vb.Coordinates;
Vector3 pc = vc.Coordinates;
Vector3 pd = vd.Coordinates;
// 由当前的 y 值,我们可以计算出梯度
// 以此再计算出 起始 X(sx) 和 结束 X(ex)
// 如果 pa.Y == pb.Y 或者 pc.Y== pd.y 的话,梯度强制为 1
var gradient1 = pa.Y != pb.Y ? (data.currentY - pa.Y) / (pb.Y - pa.Y) : 1;
var gradient2 = pc.Y != pd.Y ? (data.currentY - pc.Y) / (pd.Y - pc.Y) : 1;
int sx = (int)Interpolate(pa.X, pb.X, gradient1);
int ex = (int)Interpolate(pc.X, pd.X, gradient2);
// 开始 Z 值和结束 Z 值
float z1 = Interpolate(pa.Z, pb.Z, gradient1);
float z2 = Interpolate(pc.Z, pd.Z, gradient2);
// 将法线插值到 Y 中
var snl = Interpolate(data.ndotla, data.ndotlb, gradient1);
var enl = Interpolate(data.ndotlc, data.ndotld, gradient2);
// 将纹理坐标插值到 Y 中
var su = Interpolate(data.ua, data.ub, gradient1);
var eu = Interpolate(data.uc, data.ud, gradient2);
var sv = Interpolate(data.va, data.vb, gradient1);
var ev = Interpolate(data.vc, data.vd, gradient2);
// 从左(sx) 向右(ex) 绘制一条线
for (var x = sx; x < ex; x++)
{
float gradient = (x - sx) / (float)(ex - sx);
// 将 Z 坐标、法线和纹理坐标插值到 X 中
var z = Interpolate(z1, z2, gradient);
var ndotl = Interpolate(snl, enl, gradient);
var u = Interpolate(su, eu, gradient);
var v = Interpolate(sv, ev, gradient);
Color4 textureColor;
if (texture != null)
textureColor = texture.Map(u, v);
else
textureColor = new Color4(1, 1, 1, 1);
// 使用光向量、法线向量的角度余弦值以及材质颜色来改变原本颜色值
DrawPoint(new Vector3(x, data.currentY, z), color * ndotl * textureColor);
}
}
【译者注:TypeScript 代码】
public processScanLine(data: ScanLineData, va: Vertex, vb: Vertex, vc: Vertex, vd: Vertex, color: BABYLON.Color4, texture?: Texture): void {
var pa = va.Coordinates;
var pb = vb.Coordinates;
var pc = vc.Coordinates;
var pd = vd.Coordinates;
// 由当前的 y 值,我们可以计算出梯度
// 以此再计算出 起始 X(sx) 和 结束 X(ex)
// 如果 pa.Y == pb.Y 或者 pc.Y== pd.y 的话,梯度强制为 1
var gradient1 = pa.y != pb.y ? (data.currentY - pa.y) / (pb.y - pa.y) : 1;
var gradient2 = pc.y != pd.y ? (data.currentY - pc.y) / (pd.y - pc.y) : 1;
var sx = this.interpolate(pa.x, pb.x, gradient1) >> 0;
var ex = this.interpolate(pc.x, pd.x, gradient2) >> 0;
// 开始 Z 值和结束 Z 值
var z1: number = this.interpolate(pa.z, pb.z, gradient1);
var z2: number = this.interpolate(pc.z, pd.z, gradient2);
// 将法线插值到 Y 中
var snl = this.interpolate(data.ndotla, data.ndotlb, gradient1);
var enl = this.interpolate(data.ndotlc, data.ndotld, gradient2);
// 将纹理坐标插值到 Y 中
var su = this.interpolate(data.ua, data.ub, gradient1);
var eu = this.interpolate(data.uc, data.ud, gradient2);
var sv = this.interpolate(data.va, data.vb, gradient1);
var ev = this.interpolate(data.vc, data.vd, gradient2);
// 从左(sx) 向右(ex) 绘制一条线
for (var x = sx; x < ex; x++) {
var gradient: number = (x - sx) / (ex - sx);
// 将 Z 坐标、法线和纹理坐标插值到 X 中
var z = this.interpolate(z1, z2, gradient);
var ndotl = this.interpolate(snl, enl, gradient);
var u = this.interpolate(su, eu, gradient);
var v = this.interpolate(sv, ev, gradient);
var textureColor;
if (texture)
textureColor = texture.map(u, v);
else
textureColor = new BABYLON.Color4(1, 1, 1, 1);
// 使用光向量、法线向量的角度余弦值以及材质颜色来改变原本颜色值
this.drawPoint(new BABYLON.Vector3(x, data.currentY, z),
new BABYLON.Color4(color.r * ndotl * textureColor.r,
color.g * ndotl * textureColor.g,
color.b * ndotl * textureColor.b, 1));
}
}
【译者注:JavaScript 代码】
Device.prototype.processScanLine = function (data, va, vb, vc, vd, color, texture) {
var pa = va.Coordinates;
var pb = vb.Coordinates;
var pc = vc.Coordinates;
var pd = vd.Coordinates;
// 由当前的 y 值,我们可以计算出梯度
// 以此再计算出 起始 X(sx) 和 结束 X(ex)
// 如果 pa.Y == pb.Y 或者 pc.Y== pd.y 的话,梯度强制为 1
var gradient1 = pa.y != pb.y ? (data.currentY - pa.y) / (pb.y - pa.y) : 1;
var gradient2 = pc.y != pd.y ? (data.currentY - pc.y) / (pd.y - pc.y) : 1;
var sx = this.interpolate(pa.x, pb.x, gradient1) >> 0;
var ex = this.interpolate(pc.x, pd.x, gradient2) >> 0;
// 开始 Z 值和结束 Z 值
var z1 = this.interpolate(pa.z, pb.z, gradient1);
var z2 = this.interpolate(pc.z, pd.z, gradient2);
// 将法线插值到 Y 中
var snl = this.interpolate(data.ndotla, data.ndotlb, gradient1);
var enl = this.interpolate(data.ndotlc, data.ndotld, gradient2);
// 将纹理坐标插值到 Y 中
var su = this.interpolate(data.ua, data.ub, gradient1);
var eu = this.interpolate(data.uc, data.ud, gradient2);
var sv = this.interpolate(data.va, data.vb, gradient1);
var ev = this.interpolate(data.vc, data.vd, gradient2);
// 从左(sx) 向右(ex) 绘制一条线
for (var x = sx; x < ex; x++) {
var gradient = (x - sx) / (ex - sx);
// 将 Z 坐标、法线和纹理坐标插值到 X 中
var z = this.interpolate(z1, z2, gradient);
var ndotl = this.interpolate(snl, enl, gradient);
var u = this.interpolate(su, eu, gradient);
var v = this.interpolate(sv, ev, gradient);
var textureColor;
if (texture)
textureColor = texture.map(u, v);
else
textureColor = new BABYLON.Color4(1, 1, 1, 1);
// 使用光向量、法线向量的角度余弦值以及材质颜色来改变原本颜色值
this.drawPoint(new BABYLON.Vector3(x, data.currentY, z),
new BABYLON.Color4(color.r * ndotl * textureColor.r,
color.g * ndotl * textureColor.g,
color.b * ndotl * textureColor.b, 1));
}
};
如果你已经按照之前的所有教程建立过自己的版本,那么请下载我的解决方案进行对比并更新你的项目。
从 Babylon JSON 文件格式中载入信息
为了能够很好的渲染你在本章最开始所看到的效果,你需要加载由 Michel Rousseau 所修改的贴图以及从 Blender 导出的苏珊妮模型的最新版本。因此,请下载这两个文件:
- 从 Blender 中导出的苏珊妮模型以及纹理坐标集合: http://david.blob.core.windows.net/softengine3d/part6/monkey.babylon
- 要加载的 512x512 大小的纹理贴图: http://david.blob.core.windows.net/softengine3d/part6/Suzanne.jpg
David Catuhe 所导出的 Babylon.JSON 格式文件包含了很多我们本章节不会涵盖的细节。例如,贴材质到模型上。实际上,设计者可以使用特殊材质来贴到一个网格中。在我们的例子中,我们只打算处理漫反射纹理。如果你想要实现更多,还请以 David Catuhe 的文章作为基础: Babylon.js:在您的游戏中使用标准材质
接下来,我将只与你分享有变化的主体部分:加载和分析 JSON 文件的方法/函数。
// 以异步方式加载 JSON 文件
public async Task<Mesh[]> LoadJSONFileAsync(string fileName)
{
var meshes = new List<Mesh>();
var materials = new Dictionary<String,Material>();
var file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(fileName);
var data = await Windows.Storage.FileIO.ReadTextAsync(file);
dynamic jsonObject = Newtonsoft.Json.JsonConvert.DeserializeObject(data);
for (var materialIndex = 0; materialIndex < jsonObject.materials.Count; materialIndex++)
{
var material = new Material();
material.Name = jsonObject.materials[materialIndex].name.Value;
material.ID = jsonObject.materials[materialIndex].id.Value;
if (jsonObject.materials[materialIndex].diffuseTexture != null)
material.DiffuseTextureName = jsonObject.materials[materialIndex].diffuseTexture.name.Value;
materials.Add(material.ID, material);
}
for (var meshIndex = 0; meshIndex < jsonObject.meshes.Count; meshIndex++)
{
var verticesArray = jsonObject.meshes[meshIndex].vertices;
// 模型面
var indicesArray = jsonObject.meshes[meshIndex].indices;
var uvCount = jsonObject.meshes[meshIndex].uvCount.Value;
var verticesStep = 1;
// 在顶点数组中根据纹理坐标来使每顶点跳帧数自动选择 6、8 或 10
switch ((int)uvCount)
{
case 0:
verticesStep = 6;
break;
case 1:
verticesStep = 8;
break;
case 2:
verticesStep = 10;
break;
}
// 有趣的顶点信息数字
var verticesCount = verticesArray.Count / verticesStep;
// 面数是逻辑上的大小除以 3(A,B,C) 得到
var facesCount = indicesArray.Count / 3;
var mesh = new Mesh(jsonObject.meshes[meshIndex].name.Value, verticesCount, facesCount);
// 首先我们填充网格的顶点数组
for (var index = 0; index < verticesCount; index++)
{
var x = (float)verticesArray[index * verticesStep].Value;
var y = (float)verticesArray[index * verticesStep + 1].Value;
var z = (float)verticesArray[index * verticesStep + 2].Value;
// 根据 Blender 导出的信息中加载顶点法线
var nx = (float)verticesArray[index * verticesStep + 3].Value;
var ny = (float)verticesArray[index * verticesStep + 4].Value;
var nz = (float)verticesArray[index * verticesStep + 5].Value;
mesh.Vertices[index] = new Vertex
{
Coordinates = new Vector3(x, y, z),
Normal = new Vector3(nx, ny, nz)
};
if (uvCount > 0)
{
// 加载纹理坐标
float u = (float)verticesArray[index * verticesStep + 6].Value;
float v = (float)verticesArray[index * verticesStep + 7].Value;
mesh.Vertices[index].TextureCoordinates = new Vector2(u, v);
}
}
// 然后填充模型面数组
for (var index = 0; index < facesCount; index++)
{
var a = (int)indicesArray[index * 3].Value;
var b = (int)indicesArray[index * 3 + 1].Value;
var c = (int)indicesArray[index * 3 + 2].Value;
mesh.Faces[index] = new Face { A = a, B = b, C = c };
}
// 获取你在 Blender 中设置的位置
var position = jsonObject.meshes[meshIndex].position;
mesh.Position = new Vector3((float)position[0].Value, (float)position[1].Value, (float)position[2].Value);
if (uvCount > 0)
{
// 材质
var meshTextureID = jsonObject.meshes[meshIndex].materialId.Value;
var meshTextureName = materials[meshTextureID].DiffuseTextureName;
mesh.Texture = new Texture(meshTextureName, 512, 512);
}
meshes.Add(mesh);
}
return meshes.ToArray();
}
【译者注:TypeScript 代码】
private CreateMeshesFromJSON(jsonObject): Mesh[] {
var meshes: Mesh[] = [];
var materials: Material[] = [];
for (var materialIndex = 0; materialIndex < jsonObject.materials.length; materialIndex++) {
var material: Material = {};
material.Name = jsonObject.materials[materialIndex].name;
material.ID = jsonObject.materials[materialIndex].id;
if (jsonObject.materials[materialIndex].diffuseTexture)
material.DiffuseTextureName = jsonObject.materials[materialIndex].diffuseTexture.name;
materials[material.ID] = material;
}
for (var meshIndex = 0; meshIndex < jsonObject.meshes.length; meshIndex++) {
var verticesArray: number[] = jsonObject.meshes[meshIndex].vertices;
// 模型面
var indicesArray: number[] = jsonObject.meshes[meshIndex].indices;
var uvCount: number = jsonObject.meshes[meshIndex].uvCount;
var verticesStep = 1;
// 在顶点数组中根据纹理坐标来使每顶点跳帧数自动选择 6、8 或 10
switch (uvCount) {
case 0:
verticesStep = 6;
break;
case 1:
verticesStep = 8;
break;
case 2:
verticesStep = 10;
break;
}
// 有趣的顶点信息数字
var verticesCount = verticesArray.length / verticesStep;
// 面数是逻辑上的大小除以 3(A,B,C) 得到
var facesCount = indicesArray.length / 3;
var mesh = new SoftEngine.Mesh(jsonObject.meshes[meshIndex].name, verticesCount, facesCount);
// 首先我们填充网格的顶点数组
for (var index = 0; index < verticesCount; index++) {
var x = verticesArray[index * verticesStep];
var y = verticesArray[index * verticesStep + 1];
var z = verticesArray[index * verticesStep + 2];
// 根据 Blender 导出的信息中加载顶点法线
var nx = verticesArray[index * verticesStep + 3];
var ny = verticesArray[index * verticesStep + 4];
var nz = verticesArray[index * verticesStep + 5];
mesh.Vertices[index] = {
Coordinates: new BABYLON.Vector3(x, y, z),
Normal: new BABYLON.Vector3(nx, ny, nz)
};
if (uvCount > 0) {
// 加载纹理坐标
var u = verticesArray[index * verticesStep + 6];
var v = verticesArray[index * verticesStep + 7];
mesh.Vertices[index].TextureCoordinates = new BABYLON.Vector2(u, v);
}
else {
mesh.Vertices[index].TextureCoordinates = new BABYLON.Vector2(0, 0);
}
}
// 然后填充模型面数组
for (var index = 0; index < facesCount; index++) {
var a = indicesArray[index * 3];
var b = indicesArray[index * 3 + 1];
var c = indicesArray[index * 3 + 2];
mesh.Faces[index] = {
A: a,
B: b,
C: c
};
}
// 获取你在 Blender 中设置的位置
var position = jsonObject.meshes[meshIndex].position;
mesh.Position = new BABYLON.Vector3(position[0], position[1], position[2]);
if (uvCount > 0) {
var meshTextureID = jsonObject.meshes[meshIndex].materialId;
var meshTextureName = materials[meshTextureID].DiffuseTextureName;
mesh.Texture = new Texture(meshTextureName, 512, 512);
}
meshes.push(mesh);
}
return meshes;
}
【译者注:JavaScript 代码】
Device.prototype.CreateMeshesFromJSON = function (jsonObject) {
var meshes = [];
var materials = [];
for (var materialIndex = 0; materialIndex < jsonObject.materials.length; materialIndex++) {
var material = {};
material.Name = jsonObject.materials[materialIndex].name;
material.ID = jsonObject.materials[materialIndex].id;
if (jsonObject.materials[materialIndex].diffuseTexture)
material.DiffuseTextureName = jsonObject.materials[materialIndex].diffuseTexture.name;
materials[material.ID] = material;
}
for (var meshIndex = 0; meshIndex < jsonObject.meshes.length; meshIndex++) {
var verticesArray = jsonObject.meshes[meshIndex].vertices;
// 模型面
var indicesArray = jsonObject.meshes[meshIndex].indices;
var uvCount = jsonObject.meshes[meshIndex].uvCount;
var verticesStep = 1;
// 在顶点数组中根据纹理坐标来使每顶点跳帧数自动选择 6、8 或 10
switch (uvCount) {
case 0:
verticesStep = 6;
break;
case 1:
verticesStep = 8;
break;
case 2:
verticesStep = 10;
break;
}
// 有趣的顶点信息数字
var verticesCount = verticesArray.length / verticesStep;
// 面数是逻辑上的大小除以 3(A,B,C) 得到
var facesCount = indicesArray.length / 3;
var mesh = new SoftEngine.Mesh(jsonObject.meshes[meshIndex].name, verticesCount, facesCount);
// 首先我们填充网格的顶点数组
for (var index = 0; index < verticesCount; index++) {
var x = verticesArray[index * verticesStep];
var y = verticesArray[index * verticesStep + 1];
var z = verticesArray[index * verticesStep + 2];
// 根据 Blender 导出的信息中加载顶点法线
var nx = verticesArray[index * verticesStep + 3];
var ny = verticesArray[index * verticesStep + 4];
var nz = verticesArray[index * verticesStep + 5];
mesh.Vertices[index] = {
Coordinates: new BABYLON.Vector3(x, y, z),
Normal: new BABYLON.Vector3(nx, ny, nz)
};
if (uvCount > 0) {
// 加载纹理坐标
var u = verticesArray[index * verticesStep + 6];
var v = verticesArray[index * verticesStep + 7];
mesh.Vertices[index].TextureCoordinates = new BABYLON.Vector2(u, v);
}
else {
mesh.Vertices[index].TextureCoordinates = new BABYLON.Vector2(0, 0);
}
}
// 然后填充模型面数组
for (var index = 0; index < facesCount; index++) {
var a = indicesArray[index * 3];
var b = indicesArray[index * 3 + 1];
var c = indicesArray[index * 3 + 2];
mesh.Faces[index] = {
A: a,
B: b,
C: c
};
}
// 获取你在 Blender 中设置的位置
var position = jsonObject.meshes[meshIndex].position;
mesh.Position = new BABYLON.Vector3(position[0], position[1], position[2]);
if (uvCount > 0) {
var meshTextureID = jsonObject.meshes[meshIndex].materialId;
var meshTextureName = materials[meshTextureID].DiffuseTextureName;
mesh.Texture = new Texture(meshTextureName, 512, 512);
}
meshes.push(mesh);
}
return meshes;
};
有了这些修改,我们现在可以看到这个使用高氏着色算法渲染出美丽的苏珊妮模型了:
3D 软件渲染引擎: 在浏览器中使用 Html5 查看苏珊妮纹理和高氏着色示例
你可以在这里下载执行这一纹理映射算法解决方案:
TypeScript: SoftEngineTSPart6Sample1.zip
- JavaScript: SoftEngineJSPart6Sample1.zip
性能差异不会很大。在我的机器中 C#版本用 1600x900 的分辨率以 18 帧每秒的速度运行,而 用 Html5 版本用 640x480 的分辨率以 15 帧每秒的速度运行在 IE11 中。
在使用 GPU 方案之前,让我们来看看你的 3D 软件渲染引擎最终优化方案。
背面剔除
让我们再次从维基百科的定义开始: 背面剔除 :”在 计算机图形学 中,背面剔除用来确定图形对象的 多边形 是否可见实施背面剔除的一种方法是通过丢弃其 表面法线 和相机到多边形向量的 点积 大于或等于零的所有多边形“。
这个想法是我们的例子中在每个网格表面法线预计算的时候,在加载并解析阶段使用之前教程中的平面着色相同的算法来完成。一旦这样做,在 Render 方法/函数,我们将改变表面法线在世界视图(相机观看世界)的坐标,并检查它的 Z 值。如果它>=0,意味着我们不会绘制这个三角形,这个模型面在镜头中不可见。
3D 软件渲染引擎: 在浏览器中使用 Html5 查看苏珊妮纹理、高氏着色以及启用了背面剔除的示例
你可以在这里下载执行这一背面剔除解决方案:
TypeScript: SoftEngineTSPart6Sample2.zip
- JavaScript: SoftEngineJSPart6Sample2.zip
注 :你会发现我的背面剔除方案有一些小 Bug。极少数应该被绘制的三角形没有被绘制。这是因为我们应该调整法线的变换,以便考虑到摄像机当前视角。当前算法使得我们有了一个正交相机,但却有些不同。解决这个问题对你来说应该是一个很好的锻炼!
提升性能是很有意思的事,我们大约得到了 66%的性能提升,我在 IE11 环境下从平均每秒 15 帧转向启用背面剔除,从而提升到了 25 帧每秒。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论