diff --git a/reports/RelatorioFase4.pdf b/reports/RelatorioFase4.pdf new file mode 100644 index 00000000..b9d7e8bc Binary files /dev/null and b/reports/RelatorioFase4.pdf differ diff --git a/reports/RelatorioFase4.tex b/reports/RelatorioFase4.tex index bbeb7b82..fd778478 100644 --- a/reports/RelatorioFase4.tex +++ b/reports/RelatorioFase4.tex @@ -77,15 +77,15 @@ Nesta fase do trabalho prático, continuou-se o desenvolvimento dos programas \texttt{engine} e \texttt{generator}. Em particular, na \texttt{engine}, foram implementadas a leitura de texturas e a iluminação da cena. É lógico que estas funcionalidades exigiram alterações à estrutura de - armazenamento de modelos em VBOs, ao formato XML da cena, e a criação de novos \emph{shaders}, - para implementação dos modelos de iluminação e de \emph{shading} de Phong. Adicionalmente, - também foi implementado \emph{object picking} e geração automática de normais, para modelos que - não têm essa informação. Do lado do \texttt{generator}, foi necessário implementar a geração de - normais e coordenadas de textura para as figuras, bem como atualizar a geração do Sistema Solar, - para adicionar informação de texturas e iluminação. Em suma, apesar de se considerar que o - trabalho desenvolvido foi além do era pedido pelo enunciado, visto que foram utilizados - \emph{shaders}, haveria muitas possibilidades de melhorar o trabalho para hipotéticas fases - futuras (\emph{instanced rendering}, \emph{normal maps}, sombras, tesselação, \emph{etc.}). + armazenamento de modelos em VBOs, ao formato XML da cena, e também a criação de novos + \emph{shaders}, para implementação dos modelos de iluminação e de \emph{shading} de Phong. + Adicionalmente, também foi implementado \emph{object picking} e geração automática de normais, + para modelos que não têm essa informação. Do lado do \texttt{generator}, foi necessário + implementar a geração de normais e coordenadas de textura para as figuras, bem como atualizar a + geração do Sistema Solar, para adicionar informação de texturas e iluminação. Em suma, apesar de + se considerar que o trabalho desenvolvido foi além do que era pedido pelo enunciado, ainda + haveria muitas possibilidades de funcionalidades a implementar em hipotéticas fases futuras + (\emph{instanced rendering}, \emph{normal maps}, sombras, tesselação, \emph{etc.}). \end{abstract} \section{\emph{Generator}} @@ -120,105 +120,247 @@ \subsection{Formato \texttt{.3d}} uma coordenada de textura, e outro para um vetor normal. \end{itemize} -Como o \emph{parser} de ficheiros Wavefront OBJ foi reimplementado na fase anterior com base em -expressões regulares, foi trivial adicionar o suporte para coordenadas de textura e vetores normais. +Como o \emph{parser} de ficheiros Wavefront OBJ foi reimplementado na fase anterior para se basear +em expressões regulares, foi trivial adicionar o suporte para coordenadas de textura e vetores +normais. \subsection{Plano Horizontal} -{\color{red} TODO - Humberto} +A geração de normais do plano horizontal é trivial, todos os pontos partilham o mesmo vetor normal, +$(0, 1, 0)$. Quanto às coordenadas de textura, $s$ aumenta entre $0$ e $1$ à medida que $z$ aumenta, +e $t$ aumenta entre $0$ e $1$ à medida que $x$ aumenta. Na expressão abaixo, $N$ representa o número +de divisões do plano, $i$ a variável de iteração que aumenta ao longo do eixo $x$, e $j$ a variável +de iteração que aumenta ao longo do eixo $z$: + +$$ +s_j = \frac{j}{N} +\hspace{1cm} +t_i = \frac{i}{N} +\hspace{1cm} +i, j \in \lbrace 1, 2, \ldots, N \rbrace +$$ \subsection{Cubo} -{\color{red} TODO - Humberto} +Para a geração das normais do cubo, já durante a geração das posições do vértice do cubo foi +necessário gerar as seis normais, uma para cada face (ver relatório da primeira fase). A normal de +uma face é utilizada em todos os vértices dessa face. + +Em relação à geração de coordenadas de textura, foram implementados dois modos de geração. No modo +simples, a totalidade da textura é utilizada nas seis faces do cubo: tal como no plano, em cada +face, $s$ e $t$ variam entre $0$ e $1$. Já no modo \emph{multi-textured}, a cada face é aplicada uma +parte diferente da textura: + +$$ +s \in \left [ s_0, s_0 + \frac{1}{4} \right ] +\hspace{1cm} +t \in \left [ t_0, t_0 + \frac{1}{3} \right ], +$$ + +tal que $s_0$ e $t_0$ assumem diferentes valores para diferentes faces, de modo que a textura é +aplicada às faces do cubo como descreve a imagem abaixo: + +\begin{figure}[H] + \centering + \includegraphics[width=0.35\textwidth]{res/phase4/BoxOrientation.png} + \caption{Identificação das faces de um cubo numa textura para o seu modo \emph{multi-textured}.} +\end{figure} \subsection{Esfera} As normais da esfera são calculadas de forma muito simples: para cada vértice, a normal é o vetor que vai do centro da esfera até esse vértice, normalizado. Como a esfera está centrada na origem, -isso equivale a normalizar o próprio vetor posição do vértice. Este método garante que todas as -normais são perpendiculares à superfície. +isso equivale a normalizar o próprio vetor posição do vértice. -As coordenadas de textura (u, v) são atribuídas com base na posição angular de cada ponto. O valor -de u varia entre 0 e 1 ao longo da longitude, e o valor de v varia entre 0 e 1 ao longo da latitude. +Em todos os pontos da esfera que não os polos, para determinar as coordenadas de textura $(s, t)$, o +valor de $s$ varia entre 0 e 1 ao longo da longitude, e o valor de $t$ varia entre 0 e 1 ao longo da +latitude: -Nos polos, como existe apenas um vértice em cada um e é necessário ligá-lo a todas as \emph{slices} -ao redor, o mesmo vértice recebe várias coordenadas de textura com valores diferentes de u. -Isto permite ``cortar'' a textura em triângulos que convergem para os polos — como se estes fossem -divididos em pequenas fatias triangulares. -Desta forma, conseguimos aplicar uma imagem 2D na superfície da esfera de forma contínua, repartindo -a textura entre os vários triângulos sem quebras visuais visíveis. +$$ +s_j = \frac{j}{N_\text{slices}} +\hspace{1cm} +t_i = \frac{i}{N_\text{stacks}} +\hspace{1cm} +i \in \lbrace 1, 2, \ldots, N_\text{stacks} \rbrace +\hspace{1cm} +j \in \lbrace 1, 2, \ldots, N_\text{slices} \rbrace +$$ + +Nos polos da esfera, como são criados triângulos, e não quadrados, o método apresentado acima não é +aplicável. Logo, o vértice do polo é replicado várias vezes, e criam-se triângulos cujo polo tem +como coordenada $s$ o valor de $s$ do ponto médio dos vértices da base, como mostra a figura abaixo: + +\begin{figure}[H] + \centering + \includegraphics[width=0.35\textwidth]{res/phase4/SphereUV.png} + \caption{Triângulos da esfera no espaço de textura.} +\end{figure} + +Matematicamente, $t$ assume o valor $1$ no polo norte e $0$ no polo sul, enquanto que $s$ é dado +pela seguinte expressão: + +$$ +s_j = \frac{j + \frac{1}{2}}{N_\text{slices}} +$$ + +Com este método, parte da textura é descartada, mas torna-se possível aplicar uma imagem 2D na +superfície da esfera de forma contínua, repartindo a textura entre os vários triângulos sem quebras +visuais visíveis. \subsection{Cone} -No cone, as normais são definidas conforme a sua geometria: +No cone, as normais são definidas de forma diferente para diferentes vértices. Em primeiro lugar, na +base do cone as normais são verticais e apontam para baixo: $(0, -1, 0)$, enquanto que na sua +superfície lateral, as normais são vetores perpendiculares a cada aresta, sendo dadas pela seguinte +expressão: -\begin{itemize} - \item Na base do cone, as normais são verticais e viradas para baixo: $(0, -1, 0)$. - \item Na superfície lateral, as normais são vetores perpendiculares à superfície - inclinada (ou seja, à geratriz do cone) e são calculadas da seguinte forma: -\[ -\vec{n} = \text{normalize}(\cos(\theta), \tfrac{r}{h}, \sin(\theta)) -\] -onde \( \theta \) é o ângulo da fatia atual ao redor do eixo \( y \), \( r \) é o raio da -base e \( h \) é a altura do cone. -Esta fórmula resulta em vetores normais inclinados corretamente em relação à superfície -lateral do cone. -\end{itemize} +$$ +\vec{n} = +\begin{bmatrix} + \cos \theta \\ + \frac{r}{h} \\ + \sin \theta +\end{bmatrix}, +$$ -As coordenadas de textura são atribuídas da seguinte forma: +onde $\theta$ é o ângulo azimutal da aresta, $r$ o raio da base e $h$ a altura do cone. É lógico que +o vetor acima deve ser normalizado antes de ser adicionado ao modelo. Analisando o vetor acima, +facilmente se conclui que os termos $\cos \theta$ e $\sin \theta$ do vetor fazem com que a sua +projeção horizontal aponte para fora da aresta a que se refere. Quanto à componente $y$ deste +vetor, considerando o plano vertical que contém o eixo do cone e a aresta, $\frac{r}{h}$ é o declive +de uma reta perpendicular à aresta, de declive $-\frac{h}{r}$. -\begin{itemize} - \item As coordenadas da base são obtidas a partir da posição do vértice no plano XZ, - centralizadas em $(0{,}5,\ 0{,}5)$ e normalizadas para o intervalo $[0,\ 1]$, - permitindo o mapeamento de uma textura circular sobre a base do cone. - \item Na lateral, o valor de $u$ varia ao longo da circunferência de acordo com o ângulo - $\theta$ em torno do eixo $y$, sendo calculado como $u = \theta / 2\pi$. O valor de $v$ - varia com a altura, indo de $v = 0$ na base até $v = 1$ no topo, de forma proporcional à - coordenada $y$ dos vértices, ou seja, $v = y/h$. -\end{itemize} +O processo explicado acima também é utilizado no vértice do cone, que é replicado várias vezes, uma +vez para cada aresta. -\subsection{Cilindro} +As coordenadas de textura são também atribuídas da forma diferente consoante a parte do cone a ser +gerada. As coordenadas da base do cone foram uma circunferência de centro +$(\frac{1}{2}, \frac{1}{2})$ tangente à fronteira da textura (raio $\frac{1}{2}$): -No cilindro, as normais são definidas conforme a sua geometria: +$$ +s_j = \frac{1 + \cos \theta_j}{2} +\hspace{1cm} +t_j = \frac{1 + \sin \theta_j}{2} +\hspace{1cm} +j \in \lbrace 1, 2, \ldots, N_\text{slices} \rbrace, +$$ -\begin{itemize} - \item Na base e no topo do cilidro, as normais são verticais: $(0, -1, 0)$ e $(0, 1, 0)$, - respetivamente. - \item Na superfície lateral, as normais são vetores radiais no plano $XZ$, ou seja, $(x, 0, z)$ - normalizado e apontam para fora. -\end{itemize} +onde $\theta$ é o ângulo azimutal. Na superfície lateral, o valor de $s$ varia ao longo da +circunferência, e $t$ varia com a altura: -As coordenadas de textura são atribuídas de duas formas, dependendo do modo: +$$ +s_j = \frac{j}{N_\text{slices}} +\hspace{1cm} +t_i = \frac{i}{N_\text{stacks}} +\hspace{1cm} +i \in \lbrace 1, 2, \ldots, N_\text{stacks} \rbrace +\hspace{1cm} +j \in \lbrace 1, 2, \ldots, N_\text{slices} \rbrace +$$ -\begin{itemize} - \item No modo simples, as coordenadas da base e do topo são calculadas a partir da posição do - vértice no plano $XZ$, centradas em $(0.5, 0.5)$. Na lateral, o valor de $u$ varia ao longo da - circunferência e $v$ varia com a altura. - \item No modo \texttt{multiTextured}, aplicam-se as mesmas regras, mas os valores são ajustados - para percorrer corretamente sobre uma imagem maior que pode conter várias secções, como base, - lateral e topo, numa única textura. -\end{itemize} +Tal como acontece na esfera, parte da textura será descartada junto aos polos. + +\subsection{Cilindro} + +No cilindro, as normais são definidas conforme a parte do modelo a ser gerada. Nas bases inferior e +superior do cilindro, as normais são $(0, -1, 0)$ e $(0, 1, 0)$, respetivamente. Na superfície +lateral do cilindro, as normais são vetores radiais no plano $XZ$, ou seja, $(x, 0, z)$ após uma +normalização. + +Para o cilindro, foram implementados dois modos de gerar coordenadas de textura. No modo simples, as +coordenadas das bases são calculadas tal como no cone, formando uma circunferência de centro +$(0.5, 0.5)$. Na superfície lateral, o valor de $s$ varia ao longo da circunferência, e $t$ varia +com a altura, também como no cone. Já no modo \emph{multi-textured}, aplicam-se as mesmas regras, +mas os intervalos dos valores $s$ e $t$ são ajustados para percorrer apenas partes de uma imagem +maior que contém várias secções, duas bases e a superfície lateral: + +\begin{figure}[H] + \centering + \includegraphics[width=0.4\textwidth]{res/phase4/Barrel.jpg} + \caption{Textura para um cilindro \emph{multi-textured}.} +\end{figure} \subsection{\emph{Torus}} -{\color{red} TODO - Sara} +Previamente, para a geração dos pontos do \emph{torus}, já era necessário conhecer o vetor normal a +cada ponto: este corresponde ao vetor do raio da secção radial nessa \emph{slice}, normalizado: + +$$ +\hat{n} = \begin{bmatrix} + \cos \theta \cos \phi \\ + \sin \phi \\ + \sin \theta \cos \phi +\end{bmatrix}, +$$ + +onde $\theta$ representa o ângulo em torno do eixo central, e $\phi$ o ângulo na circunferência que +é a secção transversal do tubo. + +A geração de coordenadas de textura é muito simples: a coordenada $s$ aumenta à medida que se roda +um ponto à volta do eixo central do \emph{torus}, e $t$ varia entre 0 e 1 em cada secção +transversal do tubo: + +$$ +s_i = \frac{i}{N_\text{slices}} +\hspace{1cm} +t_j = \frac{j}{N_\text{sides}} +\hspace{1cm} +i \in \lbrace 1, 2, \ldots, N_\text{slices} \rbrace +\hspace{1cm} +j \in \lbrace 1, 2, \ldots, N_\text{stacks} \rbrace +$$ + +\subsection{Geração de Modelos com Base em \emph{Patches} de Bézier} -\subsection{Geração de modelos com base em \emph{patches} de Bézier} +Ao gerar uma superfície de Bézier, é necessário calcular as normais e as coordenadas de textura de +cada ponto da malha. Para calcular a normal de um ponto, é necessário conhecer em primeiro lugar as +derivadas parciais da curva em relação a $u$ e a $v$ nesse ponto: -Ao gerar uma superfície de Bézier, é necessário calcular as normais e as coordenadas de textura para -cada ponto da malha. +$$ +\def\arraystretch{1} +\frac{\partial p(u, v)}{\partial u} = +\begin{bmatrix} + 3 u^2 & 2 u & 1 & 0 +\end{bmatrix} +M \, P_c \, M^T +\begin{bmatrix} + v^3 \\ v^2 \\ v \\ 1 +\end{bmatrix}, +\hspace{1cm} +u, v \in \left [ 0, 1 \right ] +$$ -De modo que, as normais são obtidas através do produto vetorial entre as derivadas -parciais da superfície em relação a u e v. Estas derivadas representam os vetores tangentes à -superfície em cada ponto. O produto vetorial desses vetores resulta na normal, que é posteriormente -normalizada. Este processo garante uma correta iluminação durante a renderização do modelo. +$$ +\def\arraystretch{1} +\frac{\partial p(u, v)}{\partial v} = +\begin{bmatrix} + u^3 & u^2 & u & 1 +\end{bmatrix} +M \, P_c \, M^T +\begin{bmatrix} + 3 v^2 \\ 2 v \\ 1 \\ 0 +\end{bmatrix}, +\hspace{1cm} +u, v \in \left [ 0, 1 \right ] +$$ -As coordenadas de textura são diretamente baseadas nos parâmetros u e v, que variam entre 0 e 1. -Assim, cada ponto da superfície fica mapeado para uma posição correspondente numa imagem de textura. +Para uma descrição mais detalhada das expressões acima, é recomendada a consulta do relatório da +fase 3. As expressões acima são aplicadas uma vez para cada coordenada, $x$, $y$, e $z$, dando +origem a dois vetores, que se denominarão $du$ e $dv$. Estes vetores são tangentes à superfície no +ponto, pelo que o vetor normal pode ser dado por: -No entanto, durante o cálculo das derivadas parciais, valores como $u = 0$ ou $v = 0$ podem -originar problemas numéricos nas fronteiras da superfície. De modo que, para esses casos, são -utilizados valores ligeiramente acima de zero para garantir a estabilidade do cálculo das normais. +$$ +\vec{n} = dv \times du +$$ + +Logicamente, este vetor deve ser normalizado antes de ser adicionado ao modelo. Durante o cálculo +das derivadas parciais, valores como $du = \vec{0}$ ou $dv = \vec{0}$ podem surgir na fronteira da +superfície e dar originar vetores normais inválidos. Para evitar estes casos, na fronteira da +superfície são utilizados valores de $u$ e $v$ ligeiramente acima de zero para garantir a +estabilidade do cálculo das normais. + +As coordenadas de textura $s$ e $t$ são diretamente baseadas nos parâmetros $u$ e $v$ de cada +\emph{patch}, respetivamente, que variam entre 0 e 1. \subsection{Outras Figuras} @@ -230,16 +372,11 @@ \subsection{Outras Figuras} \subsection{Sistema Solar} -Nesta fase, a cena do sistema solar evoluiu significativamente com a introdução de iluminação, -texturas e materiais. Esses elementos contribuíram para uma representação mais fiel e -envolvente do sistema solar, aumentando consideravelmente o realismo da cena e proporcionando -uma experiência visual mais imersiva. - -\begin{figure}[H] - \centering - \includegraphics[width=\textwidth]{res/phase4/SolarSystem.png} - \caption{Sistema Solar.} -\end{figure} +Nesta fase, a cena do Sistema Solar evoluiu significativamente com a introdução de iluminação, +texturas e materiais. Em primeiro lugar, a cada modelo, foi adicionada uma textura. Depois, para +simular o Sol, uma \emph{point light} foi colocada na origem da cena. Para o modelo do Sol não ser +representado pela sua cor ambiente, foi-lhe adicionado um material com uma componente emissiva +branca. \section{\emph{Engine}} @@ -289,7 +426,24 @@ \subsection{Geração Automática de Normais} desse ponto é dado por: $$ -\hat{n} = \frac{ +\hat{n} = +\frac{ + \frac{ + \sum_{f \in F} {A_f \, \hat{n}_f} + }{ + \sum_{f \in F} {A_f} + } +}{ + \left \lVert + \frac{ + \sum_{f \in F} {A_f \, \hat{n}_f} + }{ + \sum_{f \in F} {A_f} + } + \right \rVert +} += +\frac{ \sum_{f \in F} {A_f \, \hat{n}_f} }{ \lVert \sum_{f \in F} {A_f \, \hat{n}_f} \rVert @@ -297,12 +451,11 @@ \subsection{Geração Automática de Normais} $$ Em termos de implementação deste algoritmo, um dicionário é utilizado para armazenar associações -entre posições de pontos e pares normal-área. Iteram-se por todas as faces do modelo e, para -cada face, calcula-se a sua normal e a sua área. Depois, para cada ponto nessa face, adicionam-se -aos valores armazenados de normal e de área $A \, \hat{n}$ e $A$ respetivamente. Após iterar por -todas as faces, itera-se por todas as posições no dicionário, e define-se a normal de cada ponto -como o quociente entre a normal armazenada e a área total. Logicamente, este vetor deve ser -normalizado antes de adicionado ao modelo. +entre posições de pontos e normais pesadas. Iteram-se por todas as faces do modelo e, para cada +face, calcula-se a sua normal e a sua área. Para cada ponto nessa face, adiciona-se ao valor +armazenado de normal pesada $A \, \hat{n}$. Após iterar por todas as faces, itera-se por todas as +posições no dicionário, e define-se a normal de cada ponto o como resultado da normalização do vetor +normal pesado a si associado. \subsection{VBOs} @@ -339,23 +492,24 @@ \subsection{VBOs} \end{itemize} \end{itemize} -\subsection{Adição ao \emph{Schema} XML} +\subsection{Adições ao \emph{Schema} XML} -O \emph{schema} XML foi alargado para suportar materiais com múltiplas componentes de cor, texturas -e fontes de luz diversas. Estas adições permitem um controlo mais detalhado sobre o aspeto visual -dos modelos, a aplicação de texturas e a iluminação da cena. +Para suportar as funcionalidades obrigatórias desta fase do trabalho prático, o \emph{schema} XML +foi alargado para suportar materiais com múltiplas componentes de cor, texturas, e fontes de luz +diversas. \subsubsection{Materiais} -Pode ser associado a cada modelo um conjunto de propriedades de material que influenciam a forma +A cada modelo, pode ser associado um conjunto de propriedades de material, que influenciam a forma como este interage com a luz. Estas propriedades são definidas no elemento \texttt{}, onde é -possível incluir as componentes \texttt{diffuse}, \texttt{ambient}, \texttt{specular} e -\texttt{emissive}, bem como o valor de \texttt{shininess}. Caso o elemento \texttt{} não -esteja presente, são utilizados valores por omissão. +possível definir as componentes \texttt{diffuse} (componente difusa), \texttt{ambient} (componente +ambiente), \texttt{specular} (componente especular) e \texttt{emissive} (componente emissiva), bem +como o valor de \texttt{shininess}. Caso o elemento \texttt{} não esteja presente, um +conjunto pré-definido de valores são utilizados por omissão. \begin{lstlisting}[language=xml] + - @@ -366,55 +520,46 @@ \subsubsection{Materiais} \end{lstlisting} -Estas propriedades são processadas na classe \texttt{Material}, que interpreta os valores a partir -do XML através de funções auxiliares definidas no módulo \texttt{XMLUtils}. O motor de renderização -utiliza depois estes parâmetros para configurar o shader correspondente. - \subsubsection{Texturas} -Caso o elemento \texttt{} esteja presente, é carregado o ficheiro de textura especificado -e aplicado ao modelo 3D. A ausência deste elemento implica que o modelo será renderizado apenas com -base nas cores definidas no material. +O elemento \texttt{} é utilizado para definir o caminho para uma imagem a ser aplicada como +textura de um modelo. A ausência deste elemento implica que o modelo será renderizado apenas com +base nas cores definidas no seu material. \begin{lstlisting}[language=xml] + \end{lstlisting} -No carregamento da cena, o caminho da textura é resolvido com base no diretório do ficheiro XML, e -a imagem é carregada para memória. O motor assegura que a mesma textura não é carregada múltiplas -vezes, reutilizando instâncias já existentes. Esta otimização é feita através de um \texttt{map} -que armazena texturas já processadas. +No carregamento da cena, o caminho da textura é resolvido com base na diretoria do ficheiro XML. \subsubsection{Iluminação} -Foi também introduzido suporte para os diferentes tipos de luzes: pontuais, direcionais e -\emph{spotlights}. Todas as fontes de luz devem ser declaradas dentro do elemento \texttt{} -no ficheiro XML da cena. Cada elemento \texttt{} possui um atributo \texttt{type} e -argumentos adicionais consoante o tipo de luz especificado: +Nesta fase, foi adicionado suporte para três diferentes tipos de fonte de luz: \emph{point lights}, +luzes direcionais e \emph{spotlights}. Todas as fontes de luz devem ser declaradas dentro do +elemento \texttt{}, filho do elemento \texttt{world}. Cada elemento \texttt{} possui +um atributo \texttt{type}, e argumentos adicionais consoante o tipo de luz especificado: \begin{itemize} - \item \texttt{point}: requer os atributos \texttt{posX}, \texttt{posY}, \texttt{posZ} (posição); - \item \texttt{directional}: solicita \texttt{dirX}, \texttt{dirY}, \texttt{dirZ} (direção); - \item \texttt{spotlight}: necessita da posição, da direção e o ângulo de corte - (\texttt{cutoff}). + \item \texttt{point}: \texttt{posX}, \texttt{posY}, \texttt{posZ} (posição); + \item \texttt{directional}: \texttt{dirX}, \texttt{dirY}, \texttt{dirZ} (direção); + \item \texttt{spotlight}: posição e direção (como as \emph{point} e \emph{directional lights}), + bem como e o ângulo de corte (\texttt{cutoff}). \end{itemize} \begin{lstlisting}[language=xml] + - + - + \end{lstlisting} -A criação dinâmica das instâncias de luz é realizada pela \texttt{LightFactory}, com base no -atributo \texttt{type}. Cada instância herda da interface \texttt{Light}, e é posteriormente -adicionada à lista de luzes da cena. - \subsection{Texturas e Iluminação} O principal objetivo desta fase do trabalho prático é a adição de texturas e iluminação ao projeto. @@ -511,7 +656,7 @@ \subsection{Texturas e Iluminação} O contributo da $i$-ésima luz para a componente especular da cor do fragmento, $S_i$, é calculado de acordo com a seguinte expressão, onde $K_s$ e $s$ representam a componente especular e o fator -\emph{shininess} do material aplicado ao objecto, respetivamente: +\emph{shininess} do material aplicado ao objeto, respetivamente: $$ S_i = K_s \times \left ( \max \left (0, \hat{e} \cdot \hat{r} \right ) \right ) ^ s @@ -550,7 +695,7 @@ \subsection{Texturas e Iluminação} Quando um objeto é desenhado com uma textura, a cor amostrada da textura é utilizada, nas expressões acima, como a componente difusa do material, e a componente ambiente é uma fração da cor -da textura (por omissão, um quinto). +da textura (dependente do material). Abaixo, segue-se um diagrama a explicar como estes \emph{shaders} desenvolvidos se encaixam na \emph{pipeline} de renderização: @@ -563,43 +708,104 @@ \subsection{Texturas e Iluminação} \subsection{\emph{Object Picking}} -{\color{red} TODO - Sara} +Para implementar \emph{object picking}, sempre que o utilizador clica na janela da \texttt{engine}, +é criado um \emph{framebuffer} com dois \emph{attachments}, um para cor e outro para profundidade. +Depois, a cena é desenhada para este \emph{framebuffer}. A cada entidade na cena, é atribuído um +identificador sequencial, que é utilizado para determinar a cor com que será desenhado no +\emph{framebuffer}: os 24 bits menos significativos do identificador são mapeados para as +componentes R, G, e B da cor da entidade, oito \emph{bits} de cada vez. Note-se que apenas são +desenhadas as entidades da cena, e todas a uma cor sólida. + +Após a cena ser desenhada para o \emph{framebuffer}, lê-se, com a função \texttt{glReadPixels}, o +\emph{pixel} do \emph{framebuffer} na posição do rato. Depois, transforma-se esta cor novamente num +identificador, sendo assim possível identificar uma entidade com base num clique do utilizador. + +No entanto, um identificador não é algo muito útil para se apresentar ao utilizador. Logo, o formato +XML da cena foi modificado para permitir a atribuição de nomes a entidades, como mostra o exemplo +abaixo: + +\begin{lstlisting} + + + ... + +\end{lstlisting} + +Quando a cena é desenhada para \emph{object picking}, cria-se um dicionário que associa +identificadores de entidades aos seus nomes. Logo, após se ter o identificador de um objeto, é +possível aceder a este dicionário para consultar o seu nome e apresentá-lo na UI da \texttt{engine}. \section{Resultados Obtidos} -{\color{red} TODO - cada faz as suas prints, sem borda de janela, no tamanho especificado pela cena, -com a UI escondida (U), Humberto escreve} +Nesta secção, procuram-se apresentar algumas capturas de ecrã da \texttt{engine} das novas cenas +criadas e das cenas fornecidas pela docência da UC. + +\subsection{Novas Cenas e Cenas Modificadas} + +Nesta fase, foram adicionas iluminação e texturas a algumas cenas previamente criadas, e novas cenas +também foram desenvolvidas: \begin{figure}[H] \centering - \includegraphics[width=\textwidth]{res/phase4/Cone.png} - \caption{Cone.} + \includegraphics[width=0.92\textwidth]{res/phase4/results/SolarSystem.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Plane.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/BoxSingleTexture.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/BoxMultiTexture.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Sphere.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Cone.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/CylinderSingleTexture.png} \end{figure} \begin{figure}[H] \centering - \includegraphics[width=\textwidth]{res/phase4/IceCream.png} - \caption{Gelado.} + \includegraphics[width=0.3\textwidth]{res/phase4/results/CylinderMultiTexture.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Torus.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/KleinBottle.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Teapot.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Bunny.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Cow.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Dragon.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/IceCream.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/ThirdPerson.png} + \caption{Renderização das cenas da autoria do grupo de trabalho.} \end{figure} +\subsection{Cenas Fornecidas pela Docência da UC} + +A docência da UC forneceu, juntamente com o enunciado do trabalho, algumas cenas a serem testadas no +trabalho. A \texttt{engine} renderizou-as não exatamente como as capturas de ecrã fornecidas, visto +que estas foram renderizadas com \emph{shading} de Gouraud, mas a \texttt{engine} desenvolvida +implementa \emph{shading} de Phong: + \begin{figure}[H] \centering - \includegraphics[width=\textwidth]{res/phase4/IceCreamXYZ.png} - \caption{Gelado.} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Test1.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Test2.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Test3.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Test4.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Test5.png} + \includegraphics[width=0.3\textwidth]{res/phase4/results/Test6.png} + \caption{Renderização das cenas de teste fornecidas pela docência da UC.} \end{figure} +A maior diferença entre os modelos de \emph{shading} pode ser observada na cena com a +\emph{spotlight}, onde o corte entre a regiões iluminada e não iluminada é abrupto, em vez de suave. +Note-se também que as manchas especulares no cone não têm a aparência correta, visto que o número de +\emph{slices} é muito baixo, resultando numa má aproximação de um cone. Caso se desejasse gerar uma +pirâmide, as normais deveriam ser perpendiculares às faces e não às arestas. + \section{Conclusão} Em suma, considera-se que a quarta fase do trabalho prático foi concluída com sucesso. Apesar desta fase ter sido a mais exigente, requirindo alterações a diversas partes da \texttt{engine} e do \texttt{generator}, o nosso grupo foi capaz de utilizar todo o conhecimento que tem vindo a adquirir ao longo do último semestre para implementar todas as funcionalidades pedidas, e ainda algumas -adicionais! Também foi uma grande ajuda a reestruturação arquitetural do código feita na 3.ª fase, +adicionais! Foi também uma grande ajuda a reestruturação arquitetural do código feita na 3.ª fase, que tornou mais simples a adição de novas funcionalidades. -As maiores dificuldades sentidas nesta fase deram-se no \texttt{generator}, no que toca à adição de +As maiores dificuldades nesta fase sentiram-se no \texttt{generator}, no que toca à adição de coordenadas de texturas e normais, tendo sido difícil garantir que todas as figuras tinham um aspeto -correto, e descobrir a origem dos erros que se iam encontrado: coordenadas de texturas erradas +correto, e descobrir a origem dos erros que se iam encontrando: coordenadas de texturas erradas \emph{vs.} distorção natural inevitável, ou normais erradas \emph{vs.} implementação incorreta da iluminação. @@ -610,14 +816,14 @@ \section{Conclusão} (60Hz) a cena mais complexa, o Sistema Solar, que pode exigir milhares de \emph{draw calls}. Conclui-se este trabalho com grande satisfação em relação ao resultado final, que se considera -cumprir as funcionalidades pedidas pelo enunciado, bem como implementar muitas outras. No entanto, -um possível ponto que poderia ser melhorado seria o subsistema de câmaras, que tem em +cumprir os requisitos colocados pelo enunciado, bem como implementar muitas outras funcionalidades. +No entanto, um possível ponto que poderia ser melhorado seria o subsistema de câmaras, que tem em falta aceleração e desaceleração suaves quando perante \emph{input} do utilizador. Apesar desta ser a última fase do trabalho prático, há muitas funcionalidades que poderiam ser implementadas caso houvesse tempo para tal em hipotéticas futuras fases, desde aspetos simples como uma \emph{skybox} e -LODs, como outros mais complexas apenas possíveis por se ter arquiteturado o projeto para usar +LODs, como outros mais complexos, apenas possíveis por se ter arquiteturado o projeto para usar \emph{shaders}, como sombras, reflexões, \emph{normals maps}, tesselação, \emph{physically based -rendering}, \emph{etc.}. +rendering}, \emph{etc.} \begingroup \section{Bibliografia} diff --git a/reports/res/phase4/Barrel.jpg b/reports/res/phase4/Barrel.jpg new file mode 100644 index 00000000..5030f361 Binary files /dev/null and b/reports/res/phase4/Barrel.jpg differ diff --git a/reports/res/phase4/BoxOrientation.png b/reports/res/phase4/BoxOrientation.png new file mode 100644 index 00000000..c09cbd77 Binary files /dev/null and b/reports/res/phase4/BoxOrientation.png differ diff --git a/reports/res/phase4/IceCreamXYZ.png b/reports/res/phase4/IceCreamXYZ.png deleted file mode 100644 index b45bf600..00000000 Binary files a/reports/res/phase4/IceCreamXYZ.png and /dev/null differ diff --git a/reports/res/phase4/SphereUV.png b/reports/res/phase4/SphereUV.png new file mode 100644 index 00000000..1286989d Binary files /dev/null and b/reports/res/phase4/SphereUV.png differ diff --git a/reports/res/phase4/results/BoxMultiTexture.png b/reports/res/phase4/results/BoxMultiTexture.png new file mode 100644 index 00000000..61045285 Binary files /dev/null and b/reports/res/phase4/results/BoxMultiTexture.png differ diff --git a/reports/res/phase4/results/BoxSingleTexture.png b/reports/res/phase4/results/BoxSingleTexture.png new file mode 100644 index 00000000..91d86d81 Binary files /dev/null and b/reports/res/phase4/results/BoxSingleTexture.png differ diff --git a/reports/res/phase4/results/Bunny.png b/reports/res/phase4/results/Bunny.png new file mode 100644 index 00000000..efc5ea7d Binary files /dev/null and b/reports/res/phase4/results/Bunny.png differ diff --git a/reports/res/phase4/Cone.png b/reports/res/phase4/results/Cone.png similarity index 100% rename from reports/res/phase4/Cone.png rename to reports/res/phase4/results/Cone.png diff --git a/reports/res/phase4/results/Cow.png b/reports/res/phase4/results/Cow.png new file mode 100644 index 00000000..bb6a67dc Binary files /dev/null and b/reports/res/phase4/results/Cow.png differ diff --git a/reports/res/phase4/CylinderMultiTexture.png b/reports/res/phase4/results/CylinderMultiTexture.png similarity index 100% rename from reports/res/phase4/CylinderMultiTexture.png rename to reports/res/phase4/results/CylinderMultiTexture.png diff --git a/reports/res/phase4/CylinderSingleTexture.png b/reports/res/phase4/results/CylinderSingleTexture.png similarity index 100% rename from reports/res/phase4/CylinderSingleTexture.png rename to reports/res/phase4/results/CylinderSingleTexture.png diff --git a/reports/res/phase4/results/Dragon.png b/reports/res/phase4/results/Dragon.png new file mode 100644 index 00000000..ca852ede Binary files /dev/null and b/reports/res/phase4/results/Dragon.png differ diff --git a/reports/res/phase4/IceCream.png b/reports/res/phase4/results/IceCream.png similarity index 100% rename from reports/res/phase4/IceCream.png rename to reports/res/phase4/results/IceCream.png diff --git a/reports/res/phase4/KleinBottle.png b/reports/res/phase4/results/KleinBottle.png similarity index 100% rename from reports/res/phase4/KleinBottle.png rename to reports/res/phase4/results/KleinBottle.png diff --git a/reports/res/phase4/results/Plane.png b/reports/res/phase4/results/Plane.png new file mode 100644 index 00000000..1cd19dd4 Binary files /dev/null and b/reports/res/phase4/results/Plane.png differ diff --git a/reports/res/phase4/SolarSystem.png b/reports/res/phase4/results/SolarSystem.png similarity index 100% rename from reports/res/phase4/SolarSystem.png rename to reports/res/phase4/results/SolarSystem.png diff --git a/reports/res/phase4/results/Sphere.png b/reports/res/phase4/results/Sphere.png new file mode 100644 index 00000000..96707dce Binary files /dev/null and b/reports/res/phase4/results/Sphere.png differ diff --git a/reports/res/phase4/results/Teapot.png b/reports/res/phase4/results/Teapot.png new file mode 100644 index 00000000..44915f36 Binary files /dev/null and b/reports/res/phase4/results/Teapot.png differ diff --git a/reports/res/phase4/results/ThirdPerson.png b/reports/res/phase4/results/ThirdPerson.png new file mode 100644 index 00000000..f38912ae Binary files /dev/null and b/reports/res/phase4/results/ThirdPerson.png differ diff --git a/reports/res/phase4/results/Torus.png b/reports/res/phase4/results/Torus.png new file mode 100644 index 00000000..dfaaa851 Binary files /dev/null and b/reports/res/phase4/results/Torus.png differ diff --git a/res/scenes/bunny.xml b/res/scenes/bunny.xml index 6a20c45b..95128353 100644 --- a/res/scenes/bunny.xml +++ b/res/scenes/bunny.xml @@ -6,9 +6,20 @@ + + + - + + + + + + + + + - \ No newline at end of file + diff --git a/res/scenes/cow.xml b/res/scenes/cow.xml index c155f61e..3821ee09 100644 --- a/res/scenes/cow.xml +++ b/res/scenes/cow.xml @@ -6,9 +6,20 @@ + + + - + + + + + + + + + diff --git a/res/scenes/dragon.xml b/res/scenes/dragon.xml index 5a7cc57f..afaa680c 100644 --- a/res/scenes/dragon.xml +++ b/res/scenes/dragon.xml @@ -6,9 +6,20 @@ + + + - + + + + + + + + + - \ No newline at end of file + diff --git a/res/scenes/gear.xml b/res/scenes/gear.xml index 49da42c0..da38115b 100644 --- a/res/scenes/gear.xml +++ b/res/scenes/gear.xml @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/res/scenes/homer.xml b/res/scenes/homer.xml index fa16d82f..cf07d9da 100644 --- a/res/scenes/homer.xml +++ b/res/scenes/homer.xml @@ -6,9 +6,20 @@ + + + - + + + + + + + + + - \ No newline at end of file + diff --git a/res/scenes/ice_cream.xml b/res/scenes/iceCream.xml similarity index 100% rename from res/scenes/ice_cream.xml rename to res/scenes/iceCream.xml diff --git a/res/scenes/plane.xml b/res/scenes/plane.xml index a8c2ce06..a169d838 100644 --- a/res/scenes/plane.xml +++ b/res/scenes/plane.xml @@ -14,14 +14,6 @@ /> - - - - - - - - diff --git a/res/scenes/thirdPerson.xml b/res/scenes/thirdPerson.xml index 4a19205d..d9fcf817 100644 --- a/res/scenes/thirdPerson.xml +++ b/res/scenes/thirdPerson.xml @@ -12,16 +12,31 @@ - + + + + + + + + + + + + + + - + + + diff --git a/res/textures/relva.jpg b/res/textures/relva.jpg new file mode 100644 index 00000000..f5dca6c9 Binary files /dev/null and b/res/textures/relva.jpg differ diff --git a/src/generator/figures/Cone.cpp b/src/generator/figures/Cone.cpp index 8753b9be..f681f7cf 100644 --- a/src/generator/figures/Cone.cpp +++ b/src/generator/figures/Cone.cpp @@ -49,7 +49,7 @@ Cone::Cone(float radius, float height, int slices, int stacks) { for (int iStack = 0; iStack <= stacks; iStack++) { const float y = iStack * stackStep; const float stackRadius = ((height - y) * radius) / height; - const float v = static_cast(iStack) / stacks; + const float t = static_cast(iStack) / stacks; for (int jSlice = 0; jSlice <= slices; jSlice++) { const float angle = jSlice * sliceStep; @@ -57,8 +57,8 @@ Cone::Cone(float radius, float height, int slices, int stacks) { const float z = stackRadius * sinf(angle); this->positions.push_back(glm::vec4(x, y, z, 1.0f)); - const float u = static_cast(jSlice) / slices; - this->textureCoordinates.push_back(glm::vec2(u, v)); + const float s = static_cast(jSlice) / slices; + this->textureCoordinates.push_back(glm::vec2(s, t)); } } diff --git a/src/generator/figures/Cylinder.cpp b/src/generator/figures/Cylinder.cpp index b93fc364..e3d21ddd 100644 --- a/src/generator/figures/Cylinder.cpp +++ b/src/generator/figures/Cylinder.cpp @@ -44,9 +44,9 @@ Cylinder::Cylinder(float radius, float height, int slices, int stacks, bool mult this->positions.push_back(glm::vec4(x, 0, z, 1.0f)); this->normals.push_back(glm::vec3(0.0f, -1.0f, 0.0f)); - float u = multiTextured ? (x / (2 * radius)) * 0.35f + 0.8125f : x / (2 * radius) + 0.5f; - float v = multiTextured ? (z / (2 * radius)) * 0.35f + 0.1875f : z / (2 * radius) + 0.5f; - this->textureCoordinates.push_back(glm::vec2(u, v)); + float s = multiTextured ? (x / (2 * radius)) * 0.35f + 0.8125f : x / (2 * radius) + 0.5f; + float t = multiTextured ? (z / (2 * radius)) * 0.35f + 0.1875f : z / (2 * radius) + 0.5f; + this->textureCoordinates.push_back(glm::vec2(s, t)); } for (int jSlice = 0; jSlice < slices; jSlice++) { @@ -65,7 +65,7 @@ Cylinder::Cylinder(float radius, float height, int slices, int stacks, bool mult for (int iStack = 0; iStack <= stacks; iStack++) { float y = iStack * stackStep; - float v = multiTextured ? 0.375f + (static_cast(iStack) / stacks) * (1.0f - 0.375f) : + float t = multiTextured ? 0.375f + (static_cast(iStack) / stacks) * (1.0f - 0.375f) : static_cast(iStack) / stacks; for (int jSlice = 0; jSlice <= slices; jSlice++) { @@ -73,11 +73,11 @@ Cylinder::Cylinder(float radius, float height, int slices, int stacks, bool mult float x = radius * cos(phi); float z = radius * sin(phi); - float u = 1.0f - static_cast(jSlice) / slices; + float s = 1.0f - static_cast(jSlice) / slices; this->positions.push_back(glm::vec4(x, y, z, 1.0f)); this->normals.push_back(glm::normalize(glm::vec3(x, 0.0f, z))); - this->textureCoordinates.push_back(glm::vec2(u, v)); + this->textureCoordinates.push_back(glm::vec2(s, t)); } } @@ -123,9 +123,9 @@ Cylinder::Cylinder(float radius, float height, int slices, int stacks, bool mult this->positions.push_back(glm::vec4(x, height, z, 1.0f)); this->normals.push_back(glm::vec3(0.0f, 1.0f, 0.0f)); - float u = multiTextured ? (x / (2 * radius)) * 0.35f + 0.4375f : x / (2 * radius) + 0.5f; - float v = multiTextured ? (z / (2 * radius)) * 0.35f + 0.1875f : z / (2 * radius) + 0.5f; - this->textureCoordinates.push_back(glm::vec2(u, v)); + float s = multiTextured ? (x / (2 * radius)) * 0.35f + 0.4375f : x / (2 * radius) + 0.5f; + float t = multiTextured ? (z / (2 * radius)) * 0.35f + 0.1875f : z / (2 * radius) + 0.5f; + this->textureCoordinates.push_back(glm::vec2(s, t)); } for (int jSlice = 0; jSlice < slices; jSlice++) { diff --git a/src/generator/figures/Sphere.cpp b/src/generator/figures/Sphere.cpp index c2cf33c0..0577ef97 100644 --- a/src/generator/figures/Sphere.cpp +++ b/src/generator/figures/Sphere.cpp @@ -35,9 +35,8 @@ Sphere::Sphere(float radius, int slices, int stacks) { this->positions.push_back(glm::vec4(0.0f, radius, 0.0f, 1.0f)); this->normals.push_back(glm::normalize(glm::vec3(0.0f, 1.0f, 0.0f))); for (int jSlice = 0; jSlice < slices; jSlice++) { - const float u = jSlice * textureSliceStep + textureSliceStep / 2; - - this->textureCoordinates.push_back(glm::vec2(u, 1.0f)); + const float s = jSlice * textureSliceStep + textureSliceStep / 2; + this->textureCoordinates.push_back(glm::vec2(s, 1.0f)); } // Generate center @@ -45,17 +44,17 @@ Sphere::Sphere(float radius, int slices, int stacks) { const float theta = iStack * stackStep; const float y = radius * cosf(theta); const float xz = radius * sinf(theta); - const float v = 1.0f - static_cast(iStack) / stacks; + const float t = 1.0f - static_cast(iStack) / stacks; for (int jSlice = 0; jSlice <= slices; jSlice++) { const float phi = jSlice * sliceStep; const float x = xz * sinf(phi); const float z = xz * cosf(phi); - const float u = static_cast(jSlice) / slices; + const float s = static_cast(jSlice) / slices; this->positions.push_back(glm::vec4(x, y, z, 1.0f)); this->normals.push_back(glm::normalize(glm::vec3(x, y, z))); - this->textureCoordinates.push_back(glm::vec2(u, v)); + this->textureCoordinates.push_back(glm::vec2(s, t)); } } @@ -65,9 +64,8 @@ Sphere::Sphere(float radius, int slices, int stacks) { this->normals.push_back(glm::normalize(glm::vec3(0.0f, -1.0f, 0.0f))); for (int jSlice = 0; jSlice < slices; jSlice++) { - const float u = jSlice * textureSliceStep + textureSliceStep / 2; - - this->textureCoordinates.push_back(glm::vec2(u, 0.0f)); + const float s = jSlice * textureSliceStep + textureSliceStep / 2; + this->textureCoordinates.push_back(glm::vec2(s, 0.0f)); } for (int jSlice = 0; jSlice < slices; jSlice++) { diff --git a/src/generator/figures/Torus.cpp b/src/generator/figures/Torus.cpp index 651369a0..8d7c289c 100644 --- a/src/generator/figures/Torus.cpp +++ b/src/generator/figures/Torus.cpp @@ -44,9 +44,9 @@ Torus::Torus(float majorRadius, float minorRadius, int slices, int sides) { this->positions.push_back(glm::vec4(vertex, 1.0f)); this->normals.push_back(normal); - const float u = static_cast(iSlice) / slices; - const float v = static_cast(jSide) / sides; - this->textureCoordinates.push_back(glm::vec2(u, v)); + const float s = static_cast(iSlice) / slices; + const float t = static_cast(jSide) / sides; + this->textureCoordinates.push_back(glm::vec2(s, t)); } } diff --git a/src/utils/WavefrontOBJ.cpp b/src/utils/WavefrontOBJ.cpp index 559eff88..b2fdb055 100644 --- a/src/utils/WavefrontOBJ.cpp +++ b/src/utils/WavefrontOBJ.cpp @@ -259,7 +259,6 @@ std::tuple, void WavefrontOBJ::generateNormals() { std::unordered_map averageNormals; - std::unordered_map averageWeights; for (const TriangleFace &face : this->faces) { // Get points and sides of the triangle @@ -281,20 +280,14 @@ void WavefrontOBJ::generateNormals() { // Calculate weighted average const glm::vec3 normal = glm::normalize(glm::cross(v0, v1)) * area; - averageNormals[p0] += normal; - averageWeights[p0] += area; averageNormals[p1] += normal; - averageWeights[p1] += area; averageNormals[p2] += normal; - averageWeights[p2] += area; } for (const glm::vec4 &position : this->positions) { const glm::vec3 perpendicular = averageNormals[position]; - const float weight = averageWeights[position]; - - this->normals.push_back(glm::normalize(perpendicular / weight)); + this->normals.push_back(glm::normalize(perpendicular)); } }