WebGL: Квадрат Малевича. Часть 2

Привет Всем.

Продолжу линейку статьи про WebGL, сегодня мы нарисуем прервый примитив на canvas'e. (Предыдущая статья)

Хорошо приступим сразу к делу.
Если у вас есть шаблон с предыдущего урока то воспользуйтесь им, если нет то скопируйте код с предыдущего урока.

Шейдеры:

Но перед тем как писать код нам надо об условится с некоторыми понятиями:

Шейдер, Фрагментный Шейдер, Вертекстный Шейдер, Шейдерный язык, Примитивы в WebGL.

Шейдер:


Шейдер это маленькая (хотя и иногда и комплексная) графическая программа.

Фрагментный и Вертексный Шейдер:


Вертексный шейдер — этот шейдер обрабатывает разные виды трансформаций, например матрицы геометрических фигур, вертексы(точки) фигуры, изменение позиций геометрических объектов, а также отображают матрицу проекции и т.д.

Фрагментный шейдер — это шейдер который обрабатывает каждый писксель, т.е. изменяет цвета формы, добавляет источники света, короче все трансформации с пикселями.

Шейдерный Язык:


Шейдерный язык (Мы будем рассматривать GLSL) — язык программирования описывающий шейдеры, обычно это язык Си с дополнительными математическими библиотеками. (классы vec2, vec3, vec4 векторы; mat2, mat3, mat4, матрицы)

Ну и наконец Примитивы.

Примитивы:


Примитивы — набор данных (обычно чисел), используются для отрисовки сцены.
В WebGL имеется три вида примитивов (В OpenGL'e есть также квадраты как примитивы, но WebGL основан на OpenGL ES 2.0 и не содержит этого):

Точки,
Линии,
и Треугольники (Чаще пользуются Треугольниками)

Ну вот с понятиями мы закончили, а теперь «в бесконечность и еще дальше.»)

Инициализация Шейдеров:


Приступим к инициализации шейдеров, добавим в тег head вот такой код, до кода с JS:

<script id="vertex" type="x-shader">
	attribute vec2 aVertexPosition;
	
	void main() {
		gl_Position = vec4(aVertexPosition,0.0,1.0);
	}
</script>
<script id="fragment" type="x-shader">
	#if GL_ES
	precision highp float;
	#endif
	
	uniform vec4 uColor;
	
	void main() {
		gl_FragColor = uColor;
	}
	
</script>

Что же представляет из себя этот код?
Он представляет два шейдера, первый Вертексный а второй Фрагментный.

Как обусловились Вертексный шейдер только задает позицию ну или трансформации, а здесь как раз позицию.
Хочу заметить что GLSL и Шейдеры имеют void main функции и не имеют ключевого слова «return», в этом случае шейдер должен определить переменную.
Снаружи функции определена переменная которая была установлена из JS!

А что насчет Фрагементного?
Фрагментный шейдер определяет сперва проверяет: «это GL_ES, не ошибся ли я номером?»)
Потом задает специальную переменную для GL'a.
После мы определяем цвет (Звучит неожиданно)), который тоже передается из JS.
Ну и в функции определяем переменную gl_FragColor, очивдно звучит, правда?) (Обозначает Цвет Фрагмента)

Тут мы все.

Создание, Использование и Компиляция Шейдера:


С шейдерами мы разобрались, теперь приступим к сладкому:

Но перед сладким нужно выпить чаю:
(Определите эти глобальные переменные в самом верху JS кода, рядом с script тэгом)

var v, f, vs, fs, program;
var itemSize, numItems;

var square;

Объявили, умнички)
Теперь поправим код:
(Правим функцию load())

function load() {
	canvas = document.getElementById("webgl");
	canvas.width = window.innerWidth;
	canvas.height = window.innerHeight;
	
	gl = initGL(canvas);
	
	gl.viewport(0,0,canvas.width,canvas.height); // Это,
	gl.clearColor(1.0, 1.0, 1.0, 1.0);
	gl.enable(gl.DEPTH_TEST);
	gl.depthFunc(gl.LEQUAL);
	gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
	
	initShaders(); // Это,
	initBuffers(); // Это,
	
	fatalityDraw(); // И Это.
}

А Теперь сладкое однозначно:

function initShaders() {
	v = document.getElementById("vertex").firstChild.nodeValue;
	f = document.getElementById("fragment").firstChild.nodeValue;
	
	vs = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vs,v);
	gl.compileShader(vs);
	
	fs = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fs,f);
	gl.compileShader(fs);
	
	program = gl.createProgram();
	gl.attachShader(program, vs);
	gl.attachShader(program, fs);
	gl.linkProgram(program);
	
	if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS))
		console.log(gl.getShaderInfoLog(vs));
	
	if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS))
		console.log(gl.getShaderInfoLog(fs));
	
	if (!gl.getProgramParameter(program, gl.LINK_STATUS))
	console.log(gl.getProgramInfoLog(program));
}

Первые две строки (После initShaders()), мы берем текст шейдеров.
Далее создаем шейдеры, ну а точнее их «ID», далее берем текст шейдеров, и аккуратненько кладем рядом с «ID», ну и очевидно компилируем.
Тоже самое со вторым шейдером, далее мы создаем программу, тоже как бы его «ID» и приколачиваем шейдеры, после этого посылаем WebGL'y программу.

А для подстраховки, что все как бы работает, мы в консоль отправляем ошибку, или же логи WebGL о статусе компиляции шейдеров.

Тут закончили, поехали дальше.

Квадрат Малевича на Экране:


Ну теперь держитесь, потому что начинается искусство)

Мсье знает толк в Извращениях:


«Так, дамы и господа у меня хорошая новость и плохая,
хорошая новость WebGL быстро рисует,
плохая новость, он чертовски хорош в извращениях с координатной системой!»

Ну да к вот, WebGL в отличии от стандартной координатной системой рисует капельку по другому:

В обычной системе кординат (Я говорю про которая например в CSS с left, top, right, bottom) (0,0) в левом верхнем угле.
А у WebGL'a в центре! *Смычок играет страшную музыку*
В левом верхнем угле (-1,-1) а (1,1) в нижнем правом угле.

Так что приступим чертить Мсье:

function initBuffers() {
	var aspect = canvas.width / canvas.height;

	var vertices = new Float32Array([
		-0.25, 0.25*aspect, 0.25, 0.25*aspect,  0.25,-0.25*aspect,
		-0.25, 0.25*aspect, 0.25,-0.25*aspect, -0.25,-0.25*aspect
	]);
	
	square = gl.createBuffer();
	
	gl.bindBuffer(gl.ARRAY_BUFFER,square);
	gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW);
	
	itemSize = 2;
	numItems = vertices.length / itemSize;
}

Так, первое что мы делаем это вычисляем аспект «ЭКРАНИЩА», что бы экран не развазюкал и не превратил наш КВАДРАТИЩЕ МАЛЕВИЧА в ПРЯМОУГОЛЬНИК МАЛЕВИЧА!
Очевидно переменная Square — это есть квадарат? Неа, это буффер квадрата, тот же самый «ID» только тут он «ID» квадрата.
Далее идет привязка буффера к рендереру, и закачка вертексов(точек) в рендерер.

Далее определяются очень две важных переменных, которые мы по позже используем.

Ну а теперь завершаем начатое (если вы конечно не уснули пока я все разъяснял  )

Фаталити!


Ну теперь мы нанесем категорический «удар» по WebGL'y который заставит появится квадрат.

function fatalityDraw() {
	gl.useProgram(program);
	
	program.uColor = gl.getUniformLocation(program,"uColor");
	gl.uniform4fv(program.uColor,[0.0,0.0,0.0,1.0]);
	
	program.aVertexPosition = gl.getAttribLocation(program,"aVertexPosition");
	gl.enableVertexAttribArray(program.aVertexPosition);
	gl.vertexAttribPointer(program.aVertexPosition, itemSize, gl.FLOAT, false, 0, 0);
	
	gl.drawArrays(gl.TRIANGLES, 0, numItems);
}

Очевидно первая строка после определения функции, заставляет WebGL использовать программу.
Далее мы закачеваем в Шейдер, цвет [0,0,0,1] черный.
Ну закачеваем дополнительную инфу в Vertex Shader, и рисуем) Как я долго этого ждал!)

Ну в принципе все.
Всем приятного рисования

Продолжение следует...

Код:

<!DOCTYPE html>
<html>
	<head>
		<title></title>
		<meta charset="utf-8" />
		<style>
			body {
				margin: 0px;
				overflow: hidden;
			}
		</style>
		<script id="vertex" type="x-shader">
			attribute vec2 aVertexPosition;
			
			void main() {
				gl_Position = vec4(aVertexPosition,0.0,1.0);
			}
		</script>
		<script id="fragment" type="x-shader">
			#if GL_ES
			precision highp float;
			#endif
			
			uniform vec4 uColor;
			
			void main() {
				gl_FragColor = uColor;
			}
			
		</script>
		<script type="text/javascript">
			var canvas;
			var gl;
			
			var v, f, vs, fs, program;
			var itemSize, numItems;
			
			var square;
						
			function initShaders() {
				v = document.getElementById("vertex").firstChild.nodeValue;
				f = document.getElementById("fragment").firstChild.nodeValue;
				
				vs = gl.createShader(gl.VERTEX_SHADER);
				gl.shaderSource(vs,v);
				gl.compileShader(vs);
				
				fs = gl.createShader(gl.FRAGMENT_SHADER);
				gl.shaderSource(fs,f);
				gl.compileShader(fs);
				
				program = gl.createProgram();
				gl.attachShader(program, vs);
				gl.attachShader(program, fs);
				gl.linkProgram(program);
				
				if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS))
			        console.log(gl.getShaderInfoLog(vs));
 
				if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS))
					console.log(gl.getShaderInfoLog(fs));
				
				if (!gl.getProgramParameter(program, gl.LINK_STATUS))
					console.log(gl.getProgramInfoLog(program));
			}
			
			function initBuffers() {
				var aspect = canvas.width / canvas.height;
 
				var vertices = new Float32Array([
					-0.25, 0.25*aspect, 0.25, 0.25*aspect,  0.25,-0.25*aspect,  // Triangle 1
					-0.25, 0.25*aspect, 0.25,-0.25*aspect, -0.25,-0.25*aspect   // Triangle 2
				]);
				
				square = gl.createBuffer();
				
				gl.bindBuffer(gl.ARRAY_BUFFER,square);
				gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW);
				
				itemSize = 2;
				numItems = vertices.length / itemSize;
			}
			
			function fatalityDraw() {
				gl.useProgram(program);
				
				program.uColor = gl.getUniformLocation(program,"uColor");
				gl.uniform4fv(program.uColor,[0.0,0.0,0.0,1.0]);
				
				program.aVertexPosition = gl.getAttribLocation(program,"aVertexPosition");
				gl.enableVertexAttribArray(program.aVertexPosition);
				gl.vertexAttribPointer(program.aVertexPosition, itemSize, gl.FLOAT, false, 0, 0);
				
				gl.drawArrays(gl.TRIANGLES, 0, numItems);
			}
			
			function initGL(canvas) {
				var inst = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
				
				if(!inst) {
					alert("У вас браузер не поддерживает WebGL");
				}
				
				return inst;
			}
			
			function load() {
				canvas = document.getElementById("webgl");
				canvas.width = window.innerWidth;
				canvas.height = window.innerHeight;
				
				gl = initGL(canvas);
				
				gl.viewport(0,0,canvas.width,canvas.height); // Это,
				gl.clearColor(1.0, 1.0, 1.0, 1.0);
				gl.enable(gl.DEPTH_TEST);
				gl.depthFunc(gl.LEQUAL);
				gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
				
				initShaders(); // Это,
				initBuffers(); // Это,
				
				fatalityDraw(); // И Это.
			}
		</script>
	</head>
	<body onload="load()">
		<canvas id="webgl"></canvas>
	</body>
</html>

4 комментария

avatar
  • lekzd
збс! ушел тестить
avatar
Если есть вопросы, задавайте, рад буду ответить!)
avatar
  • DnAp
Ухты, а напишите мне код чтоб квадратную карту можно было повернуть под таким углом:

Я как-то пытался, убил час и ничего не вышло(
avatar
Объясните в деталях, а то ни че не понятно) Под каким углом?
К тому ж у нас сообщество веб-мастерова, никто не за кого, ни че писать не будет)