- PVSM.RU - https://www.pvsm.ru -
Через серию статей попробую разобрать движок на webgl.
Основным требованием будет минимальный ввод данных. Ведь, грубо говоря, движок — это модель, созданная для упрощения задачи. Материал рассчитан на начинающий уровень, для тех, кто прочитал основы webgl и хочет попробовать начать работать. Таких как я.
Необходимо создать классы объектов (примитивы), которые из себя представляют набор точек. При этом примитивы должны быть независимы друг от друга. Каждый примитив можно перемещать, поворачивать вокруг центра или вокруг произвольной точки.
Необходимо создать механизм обрисовки этих объектов.
И напоследок необходимо создать что то вроде карты на которой можно установить наши объекты и по которой можно свободно перемещаться.
Механизм обрисовки, просто холст и краски для художника. Какой необходимый минимум должен ввести в конечном итоге пользователь нашего движка, чтобы у него был готов холст? По моему, это просто ссылка на холст в DOMе. А потом уже можно установить и цвет, и размер.
var scene = new Scene("webglID");
scene.setBackgroundColor([0.1,0.5,0.6,0.2]);
scene.setViewPort(300, 300);
Что может быть легче, если учесть, что нам необходима только первая строчка?
«webglID» — это id элемента canvas в котором будет происходить рисование.
Реализация данного механизма пока тоже не представляет из себя ничего сложного, ведь пока рисования не происходит.
function Scene(canvasID) {
this.backgroundColor = {red:1.0, green:1.0, blue:1.0, alpha:1.0};
this.canvas = document.getElementById(canvasID);
this.getContext();
}
Scene.prototype = {
setViewPort: function(width,height){
this.gl.viewportWidth = width;
this.gl.viewportHeight = height;
},
setBackgroundColor: function(colorVec){
if (colorVec){
if (colorVec.length > 0)
{
this.backgroundColor.red = colorVec[0];
}
if (colorVec.length > 1)
{
this.backgroundColor.green = colorVec[1];
}
if (colorVec.length > 2)
{
this.backgroundColor.blue = colorVec[2];
}
if (colorVec.alpha > 3)
{
this.backgroundColor.red = colorVec[3];
}
}
},
getContext:function(){
var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
this.gl = null;
for (var ii = 0; ii < names.length; ++ii) {
try {
this.gl = this.canvas.getContext(names[ii]);
} catch(e) {}
if (this.gl) {
break;
}
}
}
}
(Метод getContext взят из статьи [1]. (до этого просто писал — this.gl = this.canvas.getContext(«webgl»);)
Перед тем, как добавить в наш движок возможность рисования, необходимо определиться:
Для движка я выбрал рисование по индексам, как мне кажется, это очевидно. А тип фигуры — TRIANGLES. Здесь уже менее очевидно, но попробую объяснить.
Мы будем использовать один буфер для всех объектов и соответственно всех вершин и индексов. В дальнейшем, если будет возможность использовать несколько буферов — тип фигуры будет находится в самом объекте примитива. (Если такая возможность уже есть — напишите, пожалуйста, в комментариях.) При этом объекты должны быть независимы друг от друга, поэтому у нас остаётся выбор среди — LINES, TRIANGLES и POINTS. Я выбрал TRIANGLES, как заполняющаяся изнутри фигура.
Сам процесс рисования будет состоять из двух этапов — добавляем объект(ы) на сцену и рисуем всю сцену. На самом деле, в дальнейшем это будет 3 этапа — обнаружение объектов, которые нам надо нарисовать, добавление на сцену, ну и рисование всей сцены.
var vertex = [
-50,50,50,
50,50,50,
50,-50,50,
-50,-50,50
];
var indices = [0,1,3,1,2,3];
var obj = new botuObject(vertex,indices);
scene.AddObject(obj);
scene.draw();
botuObject — это наш первый примитив. Не самый изящный, ну какой есть. Он просто содержит в себе вершины и индексы, которые в него передали. По большому счету все примитивы будут содержать в себе вершины и индексы, только другие примитивы будут эти вершины рассчитывать сами, при инициализации. О них будет подробно написано в последующих статьях.
Реализация примитива:
function botuObject(vertex,indices){
this.vertex = vertex;
this.indices = indices;
this.vertex.size = 3;
}
Холст, финальная версия:
function Scene(canvasID) {
this.backgroundColor = {red:1.0, green:1.0, blue:1.0, alpha:1.0};
this.canvas = document.getElementById(canvasID);
this.getContext();
this.indicBuffer = "";
this.vecVertex = [];
this.vecIndices = [];
}
Scene.prototype = {
clear: function(){
this.indicBuffer = "";
this.vecVertex = [];
this.vecIndices = [];
},
getContext:function(){
var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
this.gl = null;
for (var ii = 0; ii < names.length; ++ii) {
try {
this.gl = this.canvas.getContext(names[ii]);
} catch(e) {}
if (this.gl) {
break;
}
}
},
initBuffers: function (vertex, indices) {
this.vertexBuffer = this.gl.createBuffer();
this.vertexBuffer.size = vertex.size;
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
this.program.botuPositionAttr = this.gl.getAttribLocation(this.program, "botuPosition");
this.gl.enableVertexAttribArray(this.program.botuPositionAttr);
this.gl.bufferData(this.gl.ARRAY_BUFFER,new Float32Array(vertex), this.gl.STATIC_DRAW);
if(indices)
{
this.indicBuffer = this.gl.createBuffer();
this.indicBuffer.numberOfItems = indices.length;
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.indicBuffer);
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), this.gl.STATIC_DRAW);
}
},
initProgram: function (vxShaderDom, frShaderDom) {
var vxShader = document.getElementById(vxShaderDom).textContent;
var frShader = document.getElementById(frShaderDom).textContent;
this.program = createProgram(this.gl,vxShader, frShader);
this.gl.useProgram(this.program);
this.program.botuPositionAttr = this.gl.getAttribLocation(this.program, "botuPosition");
this.gl.enableVertexAttribArray(this.program.botuPositionAttr);
function createProgram(context, vxs, frs) {
var prg = context.createProgram();
var VertexShader = createShader(context, context.VERTEX_SHADER, vxs);
var FragmentShader = createShader(context, context.FRAGMENT_SHADER, frs);
context.attachShader(prg,VertexShader);
context.attachShader(prg,FragmentShader);
context.linkProgram(prg);
if (!context.getProgramParameter(prg, context.LINK_STATUS)) {
alert(context.getProgramInfoLog(prg));
}
return prg;
}
function createShader(context,type,shader)
{
var sh = context.createShader(type);
context.shaderSource(sh, shader);
context.compileShader(sh);
if (!context.getShaderParameter(sh, context.COMPILE_STATUS))
{
alert(context.getShaderInfoLog(sh));
}
return sh;
}
},
attributeSetup: function (attribName, attribSize) {
var attrib = this.gl.getAttribLocation(this.program, attribName);
this.gl.enableVertexAttribArray(attrib);
this.gl.vertexAttribPointer(attrib, attribSize, this.gl.FLOAT, false, 0, 0);
return attrib;
},
setViewPort: function(width,height){
this.gl.viewportWidth = width;
this.gl.viewportHeight = height;
},
setBackgroundColor: function(colorVec){
if (colorVec){
if (colorVec.length > 0)
{
this.backgroundColor.red = colorVec[0];
}
if (colorVec.length > 1)
{
this.backgroundColor.green = colorVec[1];
}
if (colorVec.length > 2)
{
this.backgroundColor.blue = colorVec[2];
}
if (colorVec.alpha > 3)
{
this.backgroundColor.red = colorVec[3];
}
}
},
AddObject: function(botuObj){
this.vecVertex.size = botuObj.vertex.size;
var next = Math.max(this.vecVertex.length / this.vecVertex.size,0);
this.vecVertex = this.vecVertex.concat(botuObj.vertex);
this.vecIndices = this.vecIndices.concat(botuObj.indices.map(function(i){return i + next}));
this.vecVertex.size = botuObj.vertex.size;
},
draw: function () {
this.initProgram("vertexShader", "fragmentShader");
this.initBuffers(this.vecVertex, this.vecIndices);
this.gl.viewport(0, 0, this.gl.viewportWidth, this.gl.viewportHeight);
this.gl.clearColor(this.backgroundColor.red,this.backgroundColor.green,this.backgroundColor.blue,this.backgroundColor.alpha);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.gl.vertexAttribPointer(this.program.botuPositionAttr,this.vertexBuffer.size,this.gl.FLOAT,false,0,0);
this.gl.enable(this.gl.DEPTH_TEST);
this.gl.drawElements(this.gl.TRIANGLES, this.indicBuffer.numberOfItems, this.gl.UNSIGNED_SHORT, 0);
}
}
Читать чужой код — самое неблагодарное дело, за исключением тех случаев, когда это код мастера. Данный код на это не претендует, поэтому вкратце о всех методах:
Итак, первый явный «недочет» движка — шейдеры. У меня они установлены следующим образом:
<script type="x-shader" id="vertexShader">
attribute vec3 botuPosition;
varying vec4 colorPos;
void main(){
colorPos = vec4(max(botuPosition.x,(-1.0) * botuPosition.x) / 200.0, max(botuPosition.y,(-1.0) * botuPosition.y) / 200.0, max(botuPosition.z,(-1.0) * botuPosition.z) / 200.0,1.0);
gl_Position = vec4(botuPosition,200);
}
</script>
<script type="x-shader" id="fragmentShader">
precision highp float;
varying vec4 colorPos;
void main(){
gl_FragColor = colorPos;
}
</script>
Это временный вариант. При добавлении текстурирования — шейдеры необходимо будет немного подправить.
В следующей статье — описание «самописной» матрицы. А также первый «нормальный» примитив — куб.
Автор: Botu
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/63024
Ссылки в тексте:
[1] статьи: http://habrahabr.ru/post/112430/
[2] Источник: http://habrahabr.ru/post/227201/
Нажмите здесь для печати.