WebGL 3D 엔진 처음부터 3부분: 메쉬 변환
격자류
솔직히 말해서, 나는 사물에 클래스를 설정하는 것을 그리 좋아하지 않는다. 왜냐하면 그것은 단지 하나의 대상 텍스트를 둘러싼 대량의 템플릿 파일이기 때문이다. 그러나 이런 상황에서 나는 이것이 의미가 있을 수 있다고 생각한다.이것은 분명히 말하지 않아도 알 수 있는 것이다. 유일하게 재미있는 것은 편의를 위해 나는 유형화된 수조 전환을 하고 있다. 나는 색인을'삼각형'이라고 부른다. 왜냐하면 그물의 각도에서 보면 이것이 바로 그것이기 때문이다.
export class Mesh {
#positions;
#colors;
#normals;
#uvs;
#triangles;
constructor(mesh){
this.positions = mesh.positions;
this.colors = mesh.colors;
this.normals = mesh.normals;
this.uvs = mesh.uvs;
this.triangles = mesh.triangles;
}
set positions(val){
this.#positions = new Float32Array(val);
}
get positions(){
return this.#positions;
}
set colors(val) {
this.#colors = new Float32Array(val);
}
get colors(){
return this.#colors;
}
set normals(val) {
this.#normals = new Float32Array(val);
}
get normals(){
return this.#normals;
}
set uvs(val) {
this.#uvs = new Float32Array(val);
}
get uvs(){
return this.#uvs;
}
set triangles(val) {
this.#triangles = new Uint16Array(val);
}
get triangles(){
return this.#triangles;
}
}
렌더러 업데이트
이제 렌더기에서 드로잉 함수를 분할합니다.새로운 connectedCallback
:
async connectedCallback() {
this.createShadowDom();
this.cacheDom();
this.attachEvents();
await this.bootGpu();
this.createMeshes();
this.setupUniforms();
this.render();
}
bootGpu
지금은 조금 줄었어요.
async bootGpu() {
this.context = this.dom.canvas.getContext("webgl");
this.program = this.context.createProgram();
const vertexShader = compileShader(this.context, `
uniform mat4 uProjectionMatrix;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
float angle = -3.1415962 / 4.0;
mat4 rotationY = mat4(
cos(angle), 0, sin(angle), 0,
0, 1, 0, 0,
-sin(angle), 0, cos(angle), 0,
0, 0, 0, 1
);
vec4 translateZ = vec4(0.0, 0.0, 2.0, 0.0);
varying mediump vec4 vColor;
void main(){
gl_Position = uProjectionMatrix * (translateZ + rotationY * vec4(aVertexPosition, 1.0));
vColor = vec4(aVertexColor, 1.0);
}
`, this.context.VERTEX_SHADER);
const fragmentShader = compileShader(this.context, `
varying lowp vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`, this.context.FRAGMENT_SHADER);
this.program = compileProgram(this.context, vertexShader, fragmentShader)
this.context.enable(this.context.CULL_FACE);
this.context.cullFace(this.context.BACK);
this.context.useProgram(this.program);
}
이것은 색기 프로그램을 컴파일하고 (현재 우리는 하나뿐이지만, 잠시 후에 재구성할 수 있습니다.) 사용 중인 것으로 설정합니다.
다음으로는 createMeshes
:
createMeshes(){
this.meshes = {
cube: new Mesh({
positions: [
//Front
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
-0.5, 0.5, -0.5,
//Back
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5
],
colors: [
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0
],
triangles: [
0, 1, 2, //front
0, 2, 3,
1, 4, 7, //right
1, 7, 2,
4, 5, 6, //back
4, 6, 7,
5, 0, 3, //left
5, 3, 6,
3, 2, 7, //top
3, 7, 6,
0, 4, 1, //bottom
0, 5, 4
]
})
};
}
참고: 마지막부터 를 따르는 경우 밑면의 휘감기 순서가 잘못됩니다.위쪽이 맞습니다.
이것은 매우 좋다. 왜냐하면 현재 격자의 모든 데이터가 한 곳에 있기 때문이다.너는 쉽게 JSON이나 다른 물건에서 그것을 불러올 수 있다고 상상할 수 있다.우리도 대상에 다른 격자를 추가한 후에 그 중에서 순환할 수 있다.setupUniforms
는 동일합니다.투영 행렬을 설정하고 있습니다.
setupUniforms(){
const projectionMatrix = new Float32Array(getProjectionMatrix(this.#height, this.#width, 90, 0.01, 100).flat());
const projectionLocation = this.context.getUniformLocation(this.program, "uProjectionMatrix");
this.context.uniformMatrix4fv(projectionLocation, false, projectionMatrix);
}
render
업데이트:
render() {
this.context.clear(this.context.COLOR_BUFFER_BIT | this.context.DEPTH_BUFFER_BIT);
for (const mesh of Object.values(this.meshes)){
this.bindMesh(mesh);
this.context.drawElements(this.context.TRIANGLES, mesh.triangles.length, this.context.UNSIGNED_SHORT, 0);
}
}
우리는 먼저 격자를 제거한 다음에 격자를 교체하고bind를 호출한 다음 그것을 그립니다.bindMesh
이것은 이전의 모든 귀속 버퍼 구역의 설정 호출이 발생한 곳이다.
bindMesh(mesh){
this.bindPositions(mesh.positions);
this.bindColors(mesh.colors);
this.bindIndices(mesh.triangles);;
}
코드의 나머지 부분은 기본적으로 같기 때문에, 유일한 차이점은 우리가 격자에서 값을 얻는 것이지, 하드코딩을 하는 것이 아니다.나는 그것들bind
을 다시 명명했다. 이것이 실제 상황이기 때문에 우리는 격자 값을 GPU의 버퍼에 연결하고 그것들을 속성과 연결시켰다.
모든 것은 예전처럼 일해야 하지만, 더욱 쉽게 처리해야 한다.입방체를 사각뿔체로 교체해 봅시다.
export const quadPyramid = {
positions: [
0.0, 0.5, 0.0,
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5
],
colors: [
1.0, 0, 0,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1
],
triangles: [
0, 1, 2,
0, 2, 3,
0, 3, 4,
0, 4, 1
]
}
격자 레벨 변환
우리도 격자 자체를 변환할 수 있다.GPU가 이 일을 더 빨리 완성할 수 있기 때문에 좋은 생각은 아니지만, 만약 당신이 생각한다면, 우리는 이 점을 추가할 수 있다. 이것은 적어도 일이 정확하다는 것을 증명하는 데 쓰일 수 있다.
메서드에 메서드translate
를 추가할 수 있습니다.
translate(x = 0, y = 0, z = 0){
for(let i = 0; i < this.#positions.length; i++){
switch(i % 3){
case 0:
this.#positions[i] += x;
break;
case 1:
this.#positions[i] += y;
break;
case 2:
this.#positions[i] += z;
break;
}
}
}
이것은 좌표를 업데이트해서 이동합니다. (마찬가지로 실천에서는 기선 좌표를 상대적으로 가운데에 두고 행렬 곱셈을 사용해서 이동하기를 원합니다.)
동일한 캔버스에 두 개의 메쉬를 배치하여 몇 가지 변경 사항만 적용할 수 있습니다.
createMeshes(){
const tcube = new Mesh(cube);
tcube.translate(0.75, 0, 0);
const tpyramid = new Mesh(quadPyramid);
tpyramid.translate(-0.75, 0, 0);
this.meshes = {
pyramid: tpyramid,
cube: tcube
};
}
이제 우리는 여러 개의 격자를 쉽게 그릴 수 있다.
초점이동, 줌 및 회전
우리로 하여금 이렇게 하게 하는 것은 진정한 방식이다.우리가 원하는 것은 격자에 있는 값을 저장하고 격자를 그릴 때, 이 필드를 표시하는 행렬을 전송해서 GPU가 정점 착색기에서 작업을 완성하고, 밑바닥 격자를 수정할 필요가 없도록 할 수 있다.초점이동, 줌 및 회전을 나타내는 메쉬에 속성을 추가합니다.
#translation = new Float32Array([0, 0, 0]);
#scale = new Float32Array([1, 1, 1]);
#rotation = new Float32Array([0, 0, 0]);
setTranslation({ x, y, z }){
if (x){
this.#translation[0] = x;
}
if (y) {
this.#translation[1] = y;
}
if (z) {
this.#translation[2] = z;
}
}
getTranslation(){
return this.#translation;
}
setScale({ x, y, z }) {
if (x) {
this.#scale[0] = x;
}
if (y) {
this.#scale[1] = y;
}
if (z) {
this.#scale[2] = z;
}
}
getScale() {
return this.#scale;
}
setRotation({ x, y, z }) {
if (x) {
this.#rotation[0] = x;
}
if (y) {
this.#rotation[1] = y;
}
if (z) {
this.#rotation[2] = z;
}
}
getRotation(){
return this.#rotation;
}
나는 네가 갱신하고 싶은 부품을 통해 인체공학에 부합하도록 해 보았다.기본 데이터 유형은 aFloat32Array
입니다. WebGL이 바인딩할 때 기대하는 것이기 때문입니다.
그런 다음 각 메쉬의 값을 결합하는 방법bindUniforms
을 만들 수 있습니다.
bindMesh(mesh){
this.bindPositions(mesh.positions);
this.bindColors(mesh.colors);
this.bindIndices(mesh.triangles);
this.bindUniforms(mesh.getTranslation(), mesh.getScale(), mesh.getRotation());
}
bindUniforms(translation, scale, rotation){
const translationLocation = this.context.getUniformLocation(this.program, "uTranslation");
this.context.uniform3fv(translationLocation, translation);
const scaleLocation = this.context.getUniformLocation(this.program, "uScale");
this.context.uniform3fv(scaleLocation, scale);
const rotationLocation = this.context.getUniformLocation(this.program, "uRotation");
this.context.uniform3fv(rotationLocation, rotation);
}
교점 셰이더에서 지원을 추가할 수 있습니다.
uniform mat4 uProjectionMatrix;
uniform vec3 uTranslation;
uniform vec3 uScale;
uniform vec3 uRotation;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
varying mediump vec4 vColor;
void main(){
mat4 translation = mat4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
uTranslation[0], uTranslation[1], uTranslation[2], 1
);
mat4 scale = mat4(
uScale[0], 0, 0, 0,
0, uScale[1], 0, 0,
0, 0, uScale[2], 0,
0, 0, 0, 1
);
mat4 rotationX = mat4(
1, 0, 0, 0,
0, cos(uRotation[0]), -sin(uRotation[0]), 0,
0, sin(uRotation[0]), cos(uRotation[0]), 0,
0, 0, 0, 1
);
mat4 rotationY = mat4(
cos(uRotation[1]), 0, sin(uRotation[1]), 0,
0, 1, 0, 0,
-sin(uRotation[1]), 0, cos(uRotation[1]), 0,
0, 0, 0, 1
);
mat4 rotationZ = mat4(
cos(uRotation[2]), -sin(uRotation[2]), 0, 0,
sin(uRotation[2]), cos(uRotation[2]), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
mat4 modelMatrix = translation * scale * rotationX * rotationY * rotationZ;
gl_Position = uProjectionMatrix * modelMatrix * vec4(aVertexPosition, 1.0);
vColor = vec4(aVertexColor, 1.0);
}
변환, 회전 및 배율 조정 행렬을 생성합니다.사실, 나는glsl이 열의 주 정렬을 사용했기 때문에 완전히 망쳤다.아이고, 이 점을 주의하십시오. (나는 왜 회전 행렬이 작동할 수 있는지 완전히 이해하지 못하지만, 정의가 정확하지 않으면 전환되지 않습니다.)참고로 평이, 회전, 축소에 쓰이는 이 행렬들은 교과서의 표준이다.나는 그들이 어떻게 일을 하는지 토론하지 않을 것이다. 왜냐하면 많은 자원이 그들을 공정하게 대할 수 있기 때문이다. 만약 당신이 그것을 필요로 한다면, 당신은 그것들을 찾을 수 있기 때문이다.
createMeshes(){
const tcube = new Mesh(cube);
tcube.setRotation({ x: Math.PI / 4, y: Math.PI / 4 });
tcube.setTranslation({ z: 2, x: 0.75 });
const tpyramid = new Mesh(quadPyramid);
tpyramid.setTranslation({ z: 2, x: -0.75 });
this.meshes = {
pyramid: tpyramid,
cube: tcube
};
}
우리는 지금 가볍게 마음대로 회전하고 평이할 수 있다.그러나 기억해 주십시오. 우리는 모양을 약간 뒤로 밀어야 합니다. 이렇게 하면 카메라가 그것들 안에 들어가지 않기 때문에translate-z가 없으면 당신은 아무것도 볼 수 없습니다!
모델 매트릭스 생성하기
이것은 좋지만 효과가 그다지 좋지 않다.우리가 하고 있는 모든 행렬 연산을 고려해 보자.만약 이 격자에 10000개의 정점이 있다면, 우리는 10000번을 할 것이다!이 경우 셰이더를 실행하기 전에 CPU에서 예상 계산을 하는 것이 좋습니다.
mat4 modelMatrix = translation * scale * rotationX * rotationY * rotationZ;
이 말은 매우 중요하다.나는 설명하지 않았지만, 우리는 기본적으로 모든 행렬을 미리 계산된 행렬로 곱할 수 있다.이 점도 매우 중요하다. 왜냐하면 순서가 매우 중요하기 때문이다.변환한 다음 회전하면 회전 후 변환과 다릅니다(이렇게 이상하게 보이면 입방체를 45도 회전한 다음 오른쪽으로 2개의 단위를 변환한 다음 원점을 중심으로 45도 회전하는 것을 고려할 수 있습니다).사실 우리는 여러 작업을 다른 순서로 반복할 수 있기 때문에 복구하는 것은 무의미하다. (비록 간단하게 보기 위해서 복구할 것이지만 GPU에서 데이터 구조로 옮기는 것은 미래의 개선을 더욱 쉽게 할 것이다.)
정의된 행렬 연산을 시작합니다.
//vector.js
export function getRotationXMatrix(theta) {
return [
[1, 0, 0, 0],
[0, Math.cos(theta), -Math.sin(theta), 0],
[0, Math.sin(theta), Math.cos(theta), 0],
[0, 0, 0, 1]
];
}
export function getRotationYMatrix(theta) {
return [
[Math.cos(theta), 0, Math.sin(theta), 0],
[0, 1, 0, 0],
[-Math.sin(theta), 0, Math.cos(theta), 0],
[0, 0, 0, 1]
];
}
export function getRotationZMatrix(theta) {
return [
[Math.cos(theta), -Math.sin(theta), 0, 0],
[Math.sin(theta), Math.cos(theta), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
}
export function getTranslationMatrix(x, y, z) {
return [
[1, 0, 0, x],
[0, 1, 0, y],
[0, 0, 1, z],
[0, 0, 0, 1]
];
}
export function getScaleMatrix(x, y, z){
return [
[x, 0, 0, 0],
[0, y, 0, 0],
[0, 0, z, 0],
[0, 0, 0, 1]
];
}
export function multiplyMatrix(a, b) {
const matrix = [
new Array(4),
new Array(4),
new Array(4),
new Array(4)
];
for (let c = 0; c < 4; c++) {
for (let r = 0; r < 4; r++) {
matrix[r][c] = a[r][0] * b[0][c] + a[r][1] * b[1][c] + a[r][2] * b[2][c] + a[r][3] * b[3][c];
}
}
return matrix;
}
export function getIdentityMatrix() {
return [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
}
export function transpose(matrix){
return [
[matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0]],
[matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1]],
[matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2]],
[matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]]
];
}
코드는 많지만 단도직입적일 것이다.mesh.js
파일에서 #rotation
, #scale
및 #translation
를 일반 배열로 변경하고 새 메서드를 추가합니다.
getModelMatrix(){
return new Float32Array(transpose(multiplyMatrix(
getRotationXMatrix(this.#translation[0]),
multiplyMatrix(
getRotationYMatrix(this.#rotation[1]),
multiplyMatrix(
getRotationZMatrix(this.#rotation[2]),
multiplyMatrix(
getScaleMatrix(this.#scale[0], this.#scale[1], this.#scale[2]),
multiplyMatrix(
getTranslationMatrix(this.#translation[0], this.#translation[1], this.#translation[2]),
getIdentityMatrix()
)
)
)
)
)).flat());
}
정말 징그럽지만, 우리는 반드시 이렇게 해야 한다.상술한 주요 원인에서 당신도 그것을 옮겨야 합니다.그것은 더 복잡한 행렬로 정리할 수 있을지도 모르지만, 나는 지금 말하지 않겠다.제복을 갱신하라. 그러면 우리는 하나밖에 없다.
bindMesh(mesh){
this.bindPositions(mesh.positions);
this.bindColors(mesh.colors);
this.bindIndices(mesh.triangles);
this.bindUniforms(mesh.getModelMatrix());
}
bindUniforms(modelMatrix){
const modelMatrixLocation = this.context.getUniformLocation(this.program, "uModelMatrix");
this.context.uniformMatrix4fv(modelMatrixLocation, false, modelMatrix);
}
그 교활한 전환 파라미터를 조심해라, 그것은 조용히 들어올 것이다.행렬에는 세 개의 매개 변수가 있다.교점 셰이더는 간단할 수도 있습니다.
uniform mat4 uProjectionMatrix;
uniform mat4 uModelMatrix;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
varying mediump vec4 vColor;
void main(){
gl_Position = uProjectionMatrix * modelMatrix * vec4(aVertexPosition, 1.0);
vColor = vec4(aVertexColor, 1.0);
}
좋아, 간단한 전환과 효율!
우리는 지금 정말로 코드펜의 제한에 대항하고 있다.아마도 다음에 우리는git 환매를 진행할 충분한 자금이 있을 것이다.
Reference
이 문제에 관하여(WebGL 3D 엔진 처음부터 3부분: 메쉬 변환), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/ndesmic/webgl-3d-engine-from-scratch-part-3-mesh-transformations-46na
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
export class Mesh {
#positions;
#colors;
#normals;
#uvs;
#triangles;
constructor(mesh){
this.positions = mesh.positions;
this.colors = mesh.colors;
this.normals = mesh.normals;
this.uvs = mesh.uvs;
this.triangles = mesh.triangles;
}
set positions(val){
this.#positions = new Float32Array(val);
}
get positions(){
return this.#positions;
}
set colors(val) {
this.#colors = new Float32Array(val);
}
get colors(){
return this.#colors;
}
set normals(val) {
this.#normals = new Float32Array(val);
}
get normals(){
return this.#normals;
}
set uvs(val) {
this.#uvs = new Float32Array(val);
}
get uvs(){
return this.#uvs;
}
set triangles(val) {
this.#triangles = new Uint16Array(val);
}
get triangles(){
return this.#triangles;
}
}
이제 렌더기에서 드로잉 함수를 분할합니다.새로운
connectedCallback
:async connectedCallback() {
this.createShadowDom();
this.cacheDom();
this.attachEvents();
await this.bootGpu();
this.createMeshes();
this.setupUniforms();
this.render();
}
bootGpu
지금은 조금 줄었어요.async bootGpu() {
this.context = this.dom.canvas.getContext("webgl");
this.program = this.context.createProgram();
const vertexShader = compileShader(this.context, `
uniform mat4 uProjectionMatrix;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
float angle = -3.1415962 / 4.0;
mat4 rotationY = mat4(
cos(angle), 0, sin(angle), 0,
0, 1, 0, 0,
-sin(angle), 0, cos(angle), 0,
0, 0, 0, 1
);
vec4 translateZ = vec4(0.0, 0.0, 2.0, 0.0);
varying mediump vec4 vColor;
void main(){
gl_Position = uProjectionMatrix * (translateZ + rotationY * vec4(aVertexPosition, 1.0));
vColor = vec4(aVertexColor, 1.0);
}
`, this.context.VERTEX_SHADER);
const fragmentShader = compileShader(this.context, `
varying lowp vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`, this.context.FRAGMENT_SHADER);
this.program = compileProgram(this.context, vertexShader, fragmentShader)
this.context.enable(this.context.CULL_FACE);
this.context.cullFace(this.context.BACK);
this.context.useProgram(this.program);
}
이것은 색기 프로그램을 컴파일하고 (현재 우리는 하나뿐이지만, 잠시 후에 재구성할 수 있습니다.) 사용 중인 것으로 설정합니다.다음으로는
createMeshes
:createMeshes(){
this.meshes = {
cube: new Mesh({
positions: [
//Front
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
-0.5, 0.5, -0.5,
//Back
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5
],
colors: [
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0
],
triangles: [
0, 1, 2, //front
0, 2, 3,
1, 4, 7, //right
1, 7, 2,
4, 5, 6, //back
4, 6, 7,
5, 0, 3, //left
5, 3, 6,
3, 2, 7, //top
3, 7, 6,
0, 4, 1, //bottom
0, 5, 4
]
})
};
}
참고: 마지막부터 를 따르는 경우 밑면의 휘감기 순서가 잘못됩니다.위쪽이 맞습니다.이것은 매우 좋다. 왜냐하면 현재 격자의 모든 데이터가 한 곳에 있기 때문이다.너는 쉽게 JSON이나 다른 물건에서 그것을 불러올 수 있다고 상상할 수 있다.우리도 대상에 다른 격자를 추가한 후에 그 중에서 순환할 수 있다.
setupUniforms
는 동일합니다.투영 행렬을 설정하고 있습니다.setupUniforms(){
const projectionMatrix = new Float32Array(getProjectionMatrix(this.#height, this.#width, 90, 0.01, 100).flat());
const projectionLocation = this.context.getUniformLocation(this.program, "uProjectionMatrix");
this.context.uniformMatrix4fv(projectionLocation, false, projectionMatrix);
}
render
업데이트:render() {
this.context.clear(this.context.COLOR_BUFFER_BIT | this.context.DEPTH_BUFFER_BIT);
for (const mesh of Object.values(this.meshes)){
this.bindMesh(mesh);
this.context.drawElements(this.context.TRIANGLES, mesh.triangles.length, this.context.UNSIGNED_SHORT, 0);
}
}
우리는 먼저 격자를 제거한 다음에 격자를 교체하고bind를 호출한 다음 그것을 그립니다.bindMesh
이것은 이전의 모든 귀속 버퍼 구역의 설정 호출이 발생한 곳이다.bindMesh(mesh){
this.bindPositions(mesh.positions);
this.bindColors(mesh.colors);
this.bindIndices(mesh.triangles);;
}
코드의 나머지 부분은 기본적으로 같기 때문에, 유일한 차이점은 우리가 격자에서 값을 얻는 것이지, 하드코딩을 하는 것이 아니다.나는 그것들bind
을 다시 명명했다. 이것이 실제 상황이기 때문에 우리는 격자 값을 GPU의 버퍼에 연결하고 그것들을 속성과 연결시켰다.모든 것은 예전처럼 일해야 하지만, 더욱 쉽게 처리해야 한다.입방체를 사각뿔체로 교체해 봅시다.
export const quadPyramid = {
positions: [
0.0, 0.5, 0.0,
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5
],
colors: [
1.0, 0, 0,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1
],
triangles: [
0, 1, 2,
0, 2, 3,
0, 3, 4,
0, 4, 1
]
}
격자 레벨 변환
우리도 격자 자체를 변환할 수 있다.GPU가 이 일을 더 빨리 완성할 수 있기 때문에 좋은 생각은 아니지만, 만약 당신이 생각한다면, 우리는 이 점을 추가할 수 있다. 이것은 적어도 일이 정확하다는 것을 증명하는 데 쓰일 수 있다.
메서드에 메서드translate
를 추가할 수 있습니다.
translate(x = 0, y = 0, z = 0){
for(let i = 0; i < this.#positions.length; i++){
switch(i % 3){
case 0:
this.#positions[i] += x;
break;
case 1:
this.#positions[i] += y;
break;
case 2:
this.#positions[i] += z;
break;
}
}
}
이것은 좌표를 업데이트해서 이동합니다. (마찬가지로 실천에서는 기선 좌표를 상대적으로 가운데에 두고 행렬 곱셈을 사용해서 이동하기를 원합니다.)
동일한 캔버스에 두 개의 메쉬를 배치하여 몇 가지 변경 사항만 적용할 수 있습니다.
createMeshes(){
const tcube = new Mesh(cube);
tcube.translate(0.75, 0, 0);
const tpyramid = new Mesh(quadPyramid);
tpyramid.translate(-0.75, 0, 0);
this.meshes = {
pyramid: tpyramid,
cube: tcube
};
}
이제 우리는 여러 개의 격자를 쉽게 그릴 수 있다.
초점이동, 줌 및 회전
우리로 하여금 이렇게 하게 하는 것은 진정한 방식이다.우리가 원하는 것은 격자에 있는 값을 저장하고 격자를 그릴 때, 이 필드를 표시하는 행렬을 전송해서 GPU가 정점 착색기에서 작업을 완성하고, 밑바닥 격자를 수정할 필요가 없도록 할 수 있다.초점이동, 줌 및 회전을 나타내는 메쉬에 속성을 추가합니다.
#translation = new Float32Array([0, 0, 0]);
#scale = new Float32Array([1, 1, 1]);
#rotation = new Float32Array([0, 0, 0]);
setTranslation({ x, y, z }){
if (x){
this.#translation[0] = x;
}
if (y) {
this.#translation[1] = y;
}
if (z) {
this.#translation[2] = z;
}
}
getTranslation(){
return this.#translation;
}
setScale({ x, y, z }) {
if (x) {
this.#scale[0] = x;
}
if (y) {
this.#scale[1] = y;
}
if (z) {
this.#scale[2] = z;
}
}
getScale() {
return this.#scale;
}
setRotation({ x, y, z }) {
if (x) {
this.#rotation[0] = x;
}
if (y) {
this.#rotation[1] = y;
}
if (z) {
this.#rotation[2] = z;
}
}
getRotation(){
return this.#rotation;
}
나는 네가 갱신하고 싶은 부품을 통해 인체공학에 부합하도록 해 보았다.기본 데이터 유형은 aFloat32Array
입니다. WebGL이 바인딩할 때 기대하는 것이기 때문입니다.
그런 다음 각 메쉬의 값을 결합하는 방법bindUniforms
을 만들 수 있습니다.
bindMesh(mesh){
this.bindPositions(mesh.positions);
this.bindColors(mesh.colors);
this.bindIndices(mesh.triangles);
this.bindUniforms(mesh.getTranslation(), mesh.getScale(), mesh.getRotation());
}
bindUniforms(translation, scale, rotation){
const translationLocation = this.context.getUniformLocation(this.program, "uTranslation");
this.context.uniform3fv(translationLocation, translation);
const scaleLocation = this.context.getUniformLocation(this.program, "uScale");
this.context.uniform3fv(scaleLocation, scale);
const rotationLocation = this.context.getUniformLocation(this.program, "uRotation");
this.context.uniform3fv(rotationLocation, rotation);
}
교점 셰이더에서 지원을 추가할 수 있습니다.
uniform mat4 uProjectionMatrix;
uniform vec3 uTranslation;
uniform vec3 uScale;
uniform vec3 uRotation;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
varying mediump vec4 vColor;
void main(){
mat4 translation = mat4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
uTranslation[0], uTranslation[1], uTranslation[2], 1
);
mat4 scale = mat4(
uScale[0], 0, 0, 0,
0, uScale[1], 0, 0,
0, 0, uScale[2], 0,
0, 0, 0, 1
);
mat4 rotationX = mat4(
1, 0, 0, 0,
0, cos(uRotation[0]), -sin(uRotation[0]), 0,
0, sin(uRotation[0]), cos(uRotation[0]), 0,
0, 0, 0, 1
);
mat4 rotationY = mat4(
cos(uRotation[1]), 0, sin(uRotation[1]), 0,
0, 1, 0, 0,
-sin(uRotation[1]), 0, cos(uRotation[1]), 0,
0, 0, 0, 1
);
mat4 rotationZ = mat4(
cos(uRotation[2]), -sin(uRotation[2]), 0, 0,
sin(uRotation[2]), cos(uRotation[2]), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
mat4 modelMatrix = translation * scale * rotationX * rotationY * rotationZ;
gl_Position = uProjectionMatrix * modelMatrix * vec4(aVertexPosition, 1.0);
vColor = vec4(aVertexColor, 1.0);
}
변환, 회전 및 배율 조정 행렬을 생성합니다.사실, 나는glsl이 열의 주 정렬을 사용했기 때문에 완전히 망쳤다.아이고, 이 점을 주의하십시오. (나는 왜 회전 행렬이 작동할 수 있는지 완전히 이해하지 못하지만, 정의가 정확하지 않으면 전환되지 않습니다.)참고로 평이, 회전, 축소에 쓰이는 이 행렬들은 교과서의 표준이다.나는 그들이 어떻게 일을 하는지 토론하지 않을 것이다. 왜냐하면 많은 자원이 그들을 공정하게 대할 수 있기 때문이다. 만약 당신이 그것을 필요로 한다면, 당신은 그것들을 찾을 수 있기 때문이다.
createMeshes(){
const tcube = new Mesh(cube);
tcube.setRotation({ x: Math.PI / 4, y: Math.PI / 4 });
tcube.setTranslation({ z: 2, x: 0.75 });
const tpyramid = new Mesh(quadPyramid);
tpyramid.setTranslation({ z: 2, x: -0.75 });
this.meshes = {
pyramid: tpyramid,
cube: tcube
};
}
우리는 지금 가볍게 마음대로 회전하고 평이할 수 있다.그러나 기억해 주십시오. 우리는 모양을 약간 뒤로 밀어야 합니다. 이렇게 하면 카메라가 그것들 안에 들어가지 않기 때문에translate-z가 없으면 당신은 아무것도 볼 수 없습니다!
모델 매트릭스 생성하기
이것은 좋지만 효과가 그다지 좋지 않다.우리가 하고 있는 모든 행렬 연산을 고려해 보자.만약 이 격자에 10000개의 정점이 있다면, 우리는 10000번을 할 것이다!이 경우 셰이더를 실행하기 전에 CPU에서 예상 계산을 하는 것이 좋습니다.
mat4 modelMatrix = translation * scale * rotationX * rotationY * rotationZ;
이 말은 매우 중요하다.나는 설명하지 않았지만, 우리는 기본적으로 모든 행렬을 미리 계산된 행렬로 곱할 수 있다.이 점도 매우 중요하다. 왜냐하면 순서가 매우 중요하기 때문이다.변환한 다음 회전하면 회전 후 변환과 다릅니다(이렇게 이상하게 보이면 입방체를 45도 회전한 다음 오른쪽으로 2개의 단위를 변환한 다음 원점을 중심으로 45도 회전하는 것을 고려할 수 있습니다).사실 우리는 여러 작업을 다른 순서로 반복할 수 있기 때문에 복구하는 것은 무의미하다. (비록 간단하게 보기 위해서 복구할 것이지만 GPU에서 데이터 구조로 옮기는 것은 미래의 개선을 더욱 쉽게 할 것이다.)
정의된 행렬 연산을 시작합니다.
//vector.js
export function getRotationXMatrix(theta) {
return [
[1, 0, 0, 0],
[0, Math.cos(theta), -Math.sin(theta), 0],
[0, Math.sin(theta), Math.cos(theta), 0],
[0, 0, 0, 1]
];
}
export function getRotationYMatrix(theta) {
return [
[Math.cos(theta), 0, Math.sin(theta), 0],
[0, 1, 0, 0],
[-Math.sin(theta), 0, Math.cos(theta), 0],
[0, 0, 0, 1]
];
}
export function getRotationZMatrix(theta) {
return [
[Math.cos(theta), -Math.sin(theta), 0, 0],
[Math.sin(theta), Math.cos(theta), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
}
export function getTranslationMatrix(x, y, z) {
return [
[1, 0, 0, x],
[0, 1, 0, y],
[0, 0, 1, z],
[0, 0, 0, 1]
];
}
export function getScaleMatrix(x, y, z){
return [
[x, 0, 0, 0],
[0, y, 0, 0],
[0, 0, z, 0],
[0, 0, 0, 1]
];
}
export function multiplyMatrix(a, b) {
const matrix = [
new Array(4),
new Array(4),
new Array(4),
new Array(4)
];
for (let c = 0; c < 4; c++) {
for (let r = 0; r < 4; r++) {
matrix[r][c] = a[r][0] * b[0][c] + a[r][1] * b[1][c] + a[r][2] * b[2][c] + a[r][3] * b[3][c];
}
}
return matrix;
}
export function getIdentityMatrix() {
return [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
}
export function transpose(matrix){
return [
[matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0]],
[matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1]],
[matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2]],
[matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]]
];
}
코드는 많지만 단도직입적일 것이다.mesh.js
파일에서 #rotation
, #scale
및 #translation
를 일반 배열로 변경하고 새 메서드를 추가합니다.
getModelMatrix(){
return new Float32Array(transpose(multiplyMatrix(
getRotationXMatrix(this.#translation[0]),
multiplyMatrix(
getRotationYMatrix(this.#rotation[1]),
multiplyMatrix(
getRotationZMatrix(this.#rotation[2]),
multiplyMatrix(
getScaleMatrix(this.#scale[0], this.#scale[1], this.#scale[2]),
multiplyMatrix(
getTranslationMatrix(this.#translation[0], this.#translation[1], this.#translation[2]),
getIdentityMatrix()
)
)
)
)
)).flat());
}
정말 징그럽지만, 우리는 반드시 이렇게 해야 한다.상술한 주요 원인에서 당신도 그것을 옮겨야 합니다.그것은 더 복잡한 행렬로 정리할 수 있을지도 모르지만, 나는 지금 말하지 않겠다.제복을 갱신하라. 그러면 우리는 하나밖에 없다.
bindMesh(mesh){
this.bindPositions(mesh.positions);
this.bindColors(mesh.colors);
this.bindIndices(mesh.triangles);
this.bindUniforms(mesh.getModelMatrix());
}
bindUniforms(modelMatrix){
const modelMatrixLocation = this.context.getUniformLocation(this.program, "uModelMatrix");
this.context.uniformMatrix4fv(modelMatrixLocation, false, modelMatrix);
}
그 교활한 전환 파라미터를 조심해라, 그것은 조용히 들어올 것이다.행렬에는 세 개의 매개 변수가 있다.교점 셰이더는 간단할 수도 있습니다.
uniform mat4 uProjectionMatrix;
uniform mat4 uModelMatrix;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
varying mediump vec4 vColor;
void main(){
gl_Position = uProjectionMatrix * modelMatrix * vec4(aVertexPosition, 1.0);
vColor = vec4(aVertexColor, 1.0);
}
좋아, 간단한 전환과 효율!
우리는 지금 정말로 코드펜의 제한에 대항하고 있다.아마도 다음에 우리는git 환매를 진행할 충분한 자금이 있을 것이다.
Reference
이 문제에 관하여(WebGL 3D 엔진 처음부터 3부분: 메쉬 변환), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/ndesmic/webgl-3d-engine-from-scratch-part-3-mesh-transformations-46na
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
translate(x = 0, y = 0, z = 0){
for(let i = 0; i < this.#positions.length; i++){
switch(i % 3){
case 0:
this.#positions[i] += x;
break;
case 1:
this.#positions[i] += y;
break;
case 2:
this.#positions[i] += z;
break;
}
}
}
createMeshes(){
const tcube = new Mesh(cube);
tcube.translate(0.75, 0, 0);
const tpyramid = new Mesh(quadPyramid);
tpyramid.translate(-0.75, 0, 0);
this.meshes = {
pyramid: tpyramid,
cube: tcube
};
}
우리로 하여금 이렇게 하게 하는 것은 진정한 방식이다.우리가 원하는 것은 격자에 있는 값을 저장하고 격자를 그릴 때, 이 필드를 표시하는 행렬을 전송해서 GPU가 정점 착색기에서 작업을 완성하고, 밑바닥 격자를 수정할 필요가 없도록 할 수 있다.초점이동, 줌 및 회전을 나타내는 메쉬에 속성을 추가합니다.
#translation = new Float32Array([0, 0, 0]);
#scale = new Float32Array([1, 1, 1]);
#rotation = new Float32Array([0, 0, 0]);
setTranslation({ x, y, z }){
if (x){
this.#translation[0] = x;
}
if (y) {
this.#translation[1] = y;
}
if (z) {
this.#translation[2] = z;
}
}
getTranslation(){
return this.#translation;
}
setScale({ x, y, z }) {
if (x) {
this.#scale[0] = x;
}
if (y) {
this.#scale[1] = y;
}
if (z) {
this.#scale[2] = z;
}
}
getScale() {
return this.#scale;
}
setRotation({ x, y, z }) {
if (x) {
this.#rotation[0] = x;
}
if (y) {
this.#rotation[1] = y;
}
if (z) {
this.#rotation[2] = z;
}
}
getRotation(){
return this.#rotation;
}
나는 네가 갱신하고 싶은 부품을 통해 인체공학에 부합하도록 해 보았다.기본 데이터 유형은 aFloat32Array
입니다. WebGL이 바인딩할 때 기대하는 것이기 때문입니다.그런 다음 각 메쉬의 값을 결합하는 방법
bindUniforms
을 만들 수 있습니다.bindMesh(mesh){
this.bindPositions(mesh.positions);
this.bindColors(mesh.colors);
this.bindIndices(mesh.triangles);
this.bindUniforms(mesh.getTranslation(), mesh.getScale(), mesh.getRotation());
}
bindUniforms(translation, scale, rotation){
const translationLocation = this.context.getUniformLocation(this.program, "uTranslation");
this.context.uniform3fv(translationLocation, translation);
const scaleLocation = this.context.getUniformLocation(this.program, "uScale");
this.context.uniform3fv(scaleLocation, scale);
const rotationLocation = this.context.getUniformLocation(this.program, "uRotation");
this.context.uniform3fv(rotationLocation, rotation);
}
교점 셰이더에서 지원을 추가할 수 있습니다.uniform mat4 uProjectionMatrix;
uniform vec3 uTranslation;
uniform vec3 uScale;
uniform vec3 uRotation;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
varying mediump vec4 vColor;
void main(){
mat4 translation = mat4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
uTranslation[0], uTranslation[1], uTranslation[2], 1
);
mat4 scale = mat4(
uScale[0], 0, 0, 0,
0, uScale[1], 0, 0,
0, 0, uScale[2], 0,
0, 0, 0, 1
);
mat4 rotationX = mat4(
1, 0, 0, 0,
0, cos(uRotation[0]), -sin(uRotation[0]), 0,
0, sin(uRotation[0]), cos(uRotation[0]), 0,
0, 0, 0, 1
);
mat4 rotationY = mat4(
cos(uRotation[1]), 0, sin(uRotation[1]), 0,
0, 1, 0, 0,
-sin(uRotation[1]), 0, cos(uRotation[1]), 0,
0, 0, 0, 1
);
mat4 rotationZ = mat4(
cos(uRotation[2]), -sin(uRotation[2]), 0, 0,
sin(uRotation[2]), cos(uRotation[2]), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
mat4 modelMatrix = translation * scale * rotationX * rotationY * rotationZ;
gl_Position = uProjectionMatrix * modelMatrix * vec4(aVertexPosition, 1.0);
vColor = vec4(aVertexColor, 1.0);
}
변환, 회전 및 배율 조정 행렬을 생성합니다.사실, 나는glsl이 열의 주 정렬을 사용했기 때문에 완전히 망쳤다.아이고, 이 점을 주의하십시오. (나는 왜 회전 행렬이 작동할 수 있는지 완전히 이해하지 못하지만, 정의가 정확하지 않으면 전환되지 않습니다.)참고로 평이, 회전, 축소에 쓰이는 이 행렬들은 교과서의 표준이다.나는 그들이 어떻게 일을 하는지 토론하지 않을 것이다. 왜냐하면 많은 자원이 그들을 공정하게 대할 수 있기 때문이다. 만약 당신이 그것을 필요로 한다면, 당신은 그것들을 찾을 수 있기 때문이다.createMeshes(){
const tcube = new Mesh(cube);
tcube.setRotation({ x: Math.PI / 4, y: Math.PI / 4 });
tcube.setTranslation({ z: 2, x: 0.75 });
const tpyramid = new Mesh(quadPyramid);
tpyramid.setTranslation({ z: 2, x: -0.75 });
this.meshes = {
pyramid: tpyramid,
cube: tcube
};
}
우리는 지금 가볍게 마음대로 회전하고 평이할 수 있다.그러나 기억해 주십시오. 우리는 모양을 약간 뒤로 밀어야 합니다. 이렇게 하면 카메라가 그것들 안에 들어가지 않기 때문에translate-z가 없으면 당신은 아무것도 볼 수 없습니다!모델 매트릭스 생성하기
이것은 좋지만 효과가 그다지 좋지 않다.우리가 하고 있는 모든 행렬 연산을 고려해 보자.만약 이 격자에 10000개의 정점이 있다면, 우리는 10000번을 할 것이다!이 경우 셰이더를 실행하기 전에 CPU에서 예상 계산을 하는 것이 좋습니다.
mat4 modelMatrix = translation * scale * rotationX * rotationY * rotationZ;
이 말은 매우 중요하다.나는 설명하지 않았지만, 우리는 기본적으로 모든 행렬을 미리 계산된 행렬로 곱할 수 있다.이 점도 매우 중요하다. 왜냐하면 순서가 매우 중요하기 때문이다.변환한 다음 회전하면 회전 후 변환과 다릅니다(이렇게 이상하게 보이면 입방체를 45도 회전한 다음 오른쪽으로 2개의 단위를 변환한 다음 원점을 중심으로 45도 회전하는 것을 고려할 수 있습니다).사실 우리는 여러 작업을 다른 순서로 반복할 수 있기 때문에 복구하는 것은 무의미하다. (비록 간단하게 보기 위해서 복구할 것이지만 GPU에서 데이터 구조로 옮기는 것은 미래의 개선을 더욱 쉽게 할 것이다.)
정의된 행렬 연산을 시작합니다.
//vector.js
export function getRotationXMatrix(theta) {
return [
[1, 0, 0, 0],
[0, Math.cos(theta), -Math.sin(theta), 0],
[0, Math.sin(theta), Math.cos(theta), 0],
[0, 0, 0, 1]
];
}
export function getRotationYMatrix(theta) {
return [
[Math.cos(theta), 0, Math.sin(theta), 0],
[0, 1, 0, 0],
[-Math.sin(theta), 0, Math.cos(theta), 0],
[0, 0, 0, 1]
];
}
export function getRotationZMatrix(theta) {
return [
[Math.cos(theta), -Math.sin(theta), 0, 0],
[Math.sin(theta), Math.cos(theta), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
}
export function getTranslationMatrix(x, y, z) {
return [
[1, 0, 0, x],
[0, 1, 0, y],
[0, 0, 1, z],
[0, 0, 0, 1]
];
}
export function getScaleMatrix(x, y, z){
return [
[x, 0, 0, 0],
[0, y, 0, 0],
[0, 0, z, 0],
[0, 0, 0, 1]
];
}
export function multiplyMatrix(a, b) {
const matrix = [
new Array(4),
new Array(4),
new Array(4),
new Array(4)
];
for (let c = 0; c < 4; c++) {
for (let r = 0; r < 4; r++) {
matrix[r][c] = a[r][0] * b[0][c] + a[r][1] * b[1][c] + a[r][2] * b[2][c] + a[r][3] * b[3][c];
}
}
return matrix;
}
export function getIdentityMatrix() {
return [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
}
export function transpose(matrix){
return [
[matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0]],
[matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1]],
[matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2]],
[matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]]
];
}
코드는 많지만 단도직입적일 것이다.mesh.js
파일에서 #rotation
, #scale
및 #translation
를 일반 배열로 변경하고 새 메서드를 추가합니다.
getModelMatrix(){
return new Float32Array(transpose(multiplyMatrix(
getRotationXMatrix(this.#translation[0]),
multiplyMatrix(
getRotationYMatrix(this.#rotation[1]),
multiplyMatrix(
getRotationZMatrix(this.#rotation[2]),
multiplyMatrix(
getScaleMatrix(this.#scale[0], this.#scale[1], this.#scale[2]),
multiplyMatrix(
getTranslationMatrix(this.#translation[0], this.#translation[1], this.#translation[2]),
getIdentityMatrix()
)
)
)
)
)).flat());
}
정말 징그럽지만, 우리는 반드시 이렇게 해야 한다.상술한 주요 원인에서 당신도 그것을 옮겨야 합니다.그것은 더 복잡한 행렬로 정리할 수 있을지도 모르지만, 나는 지금 말하지 않겠다.제복을 갱신하라. 그러면 우리는 하나밖에 없다.
bindMesh(mesh){
this.bindPositions(mesh.positions);
this.bindColors(mesh.colors);
this.bindIndices(mesh.triangles);
this.bindUniforms(mesh.getModelMatrix());
}
bindUniforms(modelMatrix){
const modelMatrixLocation = this.context.getUniformLocation(this.program, "uModelMatrix");
this.context.uniformMatrix4fv(modelMatrixLocation, false, modelMatrix);
}
그 교활한 전환 파라미터를 조심해라, 그것은 조용히 들어올 것이다.행렬에는 세 개의 매개 변수가 있다.교점 셰이더는 간단할 수도 있습니다.
uniform mat4 uProjectionMatrix;
uniform mat4 uModelMatrix;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
varying mediump vec4 vColor;
void main(){
gl_Position = uProjectionMatrix * modelMatrix * vec4(aVertexPosition, 1.0);
vColor = vec4(aVertexColor, 1.0);
}
좋아, 간단한 전환과 효율!
우리는 지금 정말로 코드펜의 제한에 대항하고 있다.아마도 다음에 우리는git 환매를 진행할 충분한 자금이 있을 것이다.
Reference
이 문제에 관하여(WebGL 3D 엔진 처음부터 3부분: 메쉬 변환), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/ndesmic/webgl-3d-engine-from-scratch-part-3-mesh-transformations-46na
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
mat4 modelMatrix = translation * scale * rotationX * rotationY * rotationZ;
//vector.js
export function getRotationXMatrix(theta) {
return [
[1, 0, 0, 0],
[0, Math.cos(theta), -Math.sin(theta), 0],
[0, Math.sin(theta), Math.cos(theta), 0],
[0, 0, 0, 1]
];
}
export function getRotationYMatrix(theta) {
return [
[Math.cos(theta), 0, Math.sin(theta), 0],
[0, 1, 0, 0],
[-Math.sin(theta), 0, Math.cos(theta), 0],
[0, 0, 0, 1]
];
}
export function getRotationZMatrix(theta) {
return [
[Math.cos(theta), -Math.sin(theta), 0, 0],
[Math.sin(theta), Math.cos(theta), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
}
export function getTranslationMatrix(x, y, z) {
return [
[1, 0, 0, x],
[0, 1, 0, y],
[0, 0, 1, z],
[0, 0, 0, 1]
];
}
export function getScaleMatrix(x, y, z){
return [
[x, 0, 0, 0],
[0, y, 0, 0],
[0, 0, z, 0],
[0, 0, 0, 1]
];
}
export function multiplyMatrix(a, b) {
const matrix = [
new Array(4),
new Array(4),
new Array(4),
new Array(4)
];
for (let c = 0; c < 4; c++) {
for (let r = 0; r < 4; r++) {
matrix[r][c] = a[r][0] * b[0][c] + a[r][1] * b[1][c] + a[r][2] * b[2][c] + a[r][3] * b[3][c];
}
}
return matrix;
}
export function getIdentityMatrix() {
return [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
}
export function transpose(matrix){
return [
[matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0]],
[matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1]],
[matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2]],
[matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]]
];
}
getModelMatrix(){
return new Float32Array(transpose(multiplyMatrix(
getRotationXMatrix(this.#translation[0]),
multiplyMatrix(
getRotationYMatrix(this.#rotation[1]),
multiplyMatrix(
getRotationZMatrix(this.#rotation[2]),
multiplyMatrix(
getScaleMatrix(this.#scale[0], this.#scale[1], this.#scale[2]),
multiplyMatrix(
getTranslationMatrix(this.#translation[0], this.#translation[1], this.#translation[2]),
getIdentityMatrix()
)
)
)
)
)).flat());
}
bindMesh(mesh){
this.bindPositions(mesh.positions);
this.bindColors(mesh.colors);
this.bindIndices(mesh.triangles);
this.bindUniforms(mesh.getModelMatrix());
}
bindUniforms(modelMatrix){
const modelMatrixLocation = this.context.getUniformLocation(this.program, "uModelMatrix");
this.context.uniformMatrix4fv(modelMatrixLocation, false, modelMatrix);
}
uniform mat4 uProjectionMatrix;
uniform mat4 uModelMatrix;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
varying mediump vec4 vColor;
void main(){
gl_Position = uProjectionMatrix * modelMatrix * vec4(aVertexPosition, 1.0);
vColor = vec4(aVertexColor, 1.0);
}
Reference
이 문제에 관하여(WebGL 3D 엔진 처음부터 3부분: 메쉬 변환), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/ndesmic/webgl-3d-engine-from-scratch-part-3-mesh-transformations-46na텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)