80befd64ae
PiperOrigin-RevId: 789723631 Change-Id: I8a5aaa78a9fe63458c07b823c7550846c14413b1
1500 lines
46 KiB
C
1500 lines
46 KiB
C
// Copyright 2021 DeepMind Technologies Limited
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "render/render_gl3.h"
|
|
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <mujoco/mjmacro.h>
|
|
#include <mujoco/mjvisualize.h>
|
|
#include <mujoco/mujoco.h>
|
|
#include "engine/engine_sort.h"
|
|
#include "engine/engine_vis_init.h"
|
|
#include "render/render_context.h"
|
|
#include "render/render_gl2.h"
|
|
#include "render/render_util.h"
|
|
#include "render/glad/glad.h"
|
|
|
|
|
|
//----------------------------- low-level 3D rendering ---------------------------------------------
|
|
|
|
// check if head is behind plane
|
|
static int isBehind(const float* headpos, const float* pos, const float* mat) {
|
|
return ((headpos[0]-pos[0])*mat[2] +
|
|
(headpos[1]-pos[1])*mat[5] +
|
|
(headpos[2]-pos[2])*mat[8] < 0.0f);
|
|
}
|
|
|
|
|
|
|
|
// check if geom is reflective
|
|
static int isReflective(const mjvGeom* geom) {
|
|
return ((geom->type == mjGEOM_PLANE || geom->type == mjGEOM_BOX) &&
|
|
!geom->transparent &&
|
|
(geom->reflectance > 0));
|
|
}
|
|
|
|
|
|
|
|
// texture types for settexture
|
|
enum {
|
|
mjtexSHADOW = 0,
|
|
mjtexSKYBOX,
|
|
mjtexREGULAR
|
|
};
|
|
|
|
|
|
// enable/disable texture mapping
|
|
static void settexture(int type, int state, const mjrContext* con, const mjvGeom* geom) {
|
|
float plane[4], scl[2];
|
|
int texid = -1;
|
|
if (geom) {
|
|
if (geom->matid >= 0) {
|
|
texid = con->mat_texid[mjNTEXROLE * geom->matid + mjTEXROLE_RGB];
|
|
}
|
|
}
|
|
|
|
// shadow
|
|
if (type == mjtexSHADOW) {
|
|
// enable
|
|
if (state) {
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glEnable(GL_TEXTURE_GEN_S);
|
|
glEnable(GL_TEXTURE_GEN_T);
|
|
glEnable(GL_TEXTURE_GEN_R);
|
|
glEnable(GL_TEXTURE_GEN_Q);
|
|
glBindTexture(GL_TEXTURE_2D, con->shadowTex);
|
|
}
|
|
|
|
// disable
|
|
else {
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glDisable(GL_TEXTURE_2D);
|
|
glDisable(GL_TEXTURE_GEN_S);
|
|
glDisable(GL_TEXTURE_GEN_T);
|
|
glDisable(GL_TEXTURE_GEN_R);
|
|
glDisable(GL_TEXTURE_GEN_Q);
|
|
}
|
|
}
|
|
|
|
// explicit texture coordinates
|
|
else if (type == mjtexREGULAR && geom->texcoord) {
|
|
// enable
|
|
if (state && texid >= 0) {
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBindTexture(GL_TEXTURE_2D, con->texture[texid]);
|
|
}
|
|
|
|
// disable
|
|
else {
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glDisable(GL_TEXTURE_2D);
|
|
}
|
|
}
|
|
|
|
// 2D
|
|
else if (type == mjtexREGULAR && texid >= 0 && con->textureType[texid] == mjTEXTURE_2D) {
|
|
// enable
|
|
if (state) {
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glEnable(GL_TEXTURE_GEN_S);
|
|
glEnable(GL_TEXTURE_GEN_T);
|
|
glBindTexture(GL_TEXTURE_2D, con->texture[texid]);
|
|
|
|
// determine scaling, adjust for pre-scaled geoms
|
|
scl[0] = con->mat_texrepeat[geom->matid*2];
|
|
scl[1] = con->mat_texrepeat[geom->matid*2+1];
|
|
if (geom->dataid >= 0) {
|
|
if (geom->size[0] > 0) {
|
|
scl[0] = scl[0] / mju_max(mjMINVAL, geom->size[0]);
|
|
}
|
|
|
|
if (geom->size[1] > 0) {
|
|
scl[1] = scl[1] / mju_max(mjMINVAL, geom->size[1]);
|
|
}
|
|
}
|
|
|
|
// uniform: repeat relative to spatial units rather than object
|
|
if (con->mat_texuniform[geom->matid]) {
|
|
if (geom->size[0] > 0) {
|
|
scl[0] = scl[0] * geom->size[0];
|
|
}
|
|
|
|
if (geom->size[1] > 0) {
|
|
scl[1] = scl[1] * geom->size[1];
|
|
}
|
|
}
|
|
|
|
// set mapping
|
|
mjr_setf4(plane, 0.5*scl[0], 0, 0, -0.5);
|
|
glTexGenfv(GL_S, GL_OBJECT_PLANE, plane);
|
|
mjr_setf4(plane, 0, -0.5*scl[1], 0, -0.5);
|
|
glTexGenfv(GL_T, GL_OBJECT_PLANE, plane);
|
|
}
|
|
|
|
// disable
|
|
else {
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glDisable(GL_TEXTURE_2D);
|
|
glDisable(GL_TEXTURE_GEN_S);
|
|
glDisable(GL_TEXTURE_GEN_T);
|
|
}
|
|
}
|
|
|
|
// cube or skybox
|
|
else {
|
|
// enable
|
|
if (state && texid >= 0) {
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glEnable(GL_TEXTURE_CUBE_MAP);
|
|
glEnable(GL_TEXTURE_GEN_S);
|
|
glEnable(GL_TEXTURE_GEN_T);
|
|
glEnable(GL_TEXTURE_GEN_R);
|
|
glBindTexture(GL_TEXTURE_CUBE_MAP, con->texture[texid]);
|
|
|
|
// set mapping : cube
|
|
if (type == mjtexREGULAR) {
|
|
mjr_setf4(plane, con->mat_texuniform[geom->matid] ? geom->size[0] : 1, 0, 0, 0);
|
|
glTexGenfv(GL_S, GL_OBJECT_PLANE, plane);
|
|
mjr_setf4(plane, 0, con->mat_texuniform[geom->matid] ? geom->size[1] : 1, 0, 0);
|
|
glTexGenfv(GL_T, GL_OBJECT_PLANE, plane);
|
|
mjr_setf4(plane, 0, 0, con->mat_texuniform[geom->matid] ? geom->size[2] : 1, 0);
|
|
glTexGenfv(GL_R, GL_OBJECT_PLANE, plane);
|
|
}
|
|
|
|
// set mapping: skybox (rotate 90 deg around X)
|
|
else {
|
|
mjr_setf4(plane, 1, 0, 0, 0);
|
|
glTexGenfv(GL_S, GL_OBJECT_PLANE, plane);
|
|
mjr_setf4(plane, 0, 0, 1, 0);
|
|
glTexGenfv(GL_T, GL_OBJECT_PLANE, plane);
|
|
mjr_setf4(plane, 0, -1, 0, 0);
|
|
glTexGenfv(GL_R, GL_OBJECT_PLANE, plane);
|
|
}
|
|
}
|
|
|
|
// disable
|
|
else {
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glDisable(GL_TEXTURE_CUBE_MAP);
|
|
glDisable(GL_TEXTURE_GEN_S);
|
|
glDisable(GL_TEXTURE_GEN_T);
|
|
glDisable(GL_TEXTURE_GEN_R);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// rendering modes for renderGeom
|
|
enum {
|
|
mjrRND_NORMAL = 0,
|
|
mjrRND_SHADOWMAP = 1,
|
|
mjrRND_SHADOWCAST = 2,
|
|
mjrRND_SEGMENT = 3,
|
|
mjrRND_IDCOLOR = 4
|
|
};
|
|
|
|
|
|
// render one geom
|
|
static void renderGeom(const mjvGeom* geom, int mode, const float* headpos,
|
|
const mjvScene* scn, const mjrContext* con) {
|
|
const float* size = geom->size;
|
|
float temp[4] = {0, 0, 0, 1}, av;
|
|
float rgba[4] = {geom->rgba[0], geom->rgba[1], geom->rgba[2], geom->rgba[3]};
|
|
int behind, whichface, lighting;
|
|
|
|
// lines and triangles do not cast shadows
|
|
if (mode == mjrRND_SHADOWCAST && (geom->type == mjGEOM_LINE || geom->category == mjCAT_DECOR)) {
|
|
return;
|
|
}
|
|
|
|
// make transformation matrix
|
|
float mat[16] = {
|
|
geom->mat[0], geom->mat[3], geom->mat[6], 0.0f,
|
|
geom->mat[1], geom->mat[4], geom->mat[7], 0.0f,
|
|
geom->mat[2], geom->mat[5], geom->mat[8], 0.0f,
|
|
0.0f, 0.0f, 0.0f, 1.0f
|
|
};
|
|
|
|
// precompute isbehind
|
|
behind = isBehind(headpos, geom->pos, geom->mat);
|
|
|
|
// enable texture in normal and shadowmap mode
|
|
if (geom->matid >= 0 &&
|
|
(mode == mjrRND_NORMAL || mode == mjrRND_SHADOWMAP)) {
|
|
settexture(mjtexREGULAR, 1, con, geom);
|
|
}
|
|
|
|
// make plane more transparent from the back
|
|
if (geom->type == mjGEOM_PLANE && behind && mode == mjrRND_NORMAL) {
|
|
rgba[3] *= 0.3;
|
|
}
|
|
|
|
// set material emission: none in shadow mode
|
|
if (mode == mjrRND_NORMAL) {
|
|
mjr_setf4(temp,
|
|
geom->emission*rgba[0],
|
|
geom->emission*rgba[1],
|
|
geom->emission*rgba[2], 1);
|
|
} else {
|
|
mjr_setf4(temp, 0, 0, 0, 1);
|
|
}
|
|
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, temp);
|
|
|
|
// set color and material
|
|
glColor4fv(rgba);
|
|
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, geom->shininess * 128.0f);
|
|
mjr_setf4(temp, geom->specular, geom->specular, geom->specular, 1);
|
|
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, temp);
|
|
|
|
// set color for segmentation mode
|
|
if (mode >= mjrRND_SEGMENT) {
|
|
// segid color
|
|
if (mode == mjrRND_IDCOLOR) {
|
|
unsigned char seg[4] = {
|
|
(geom->segid+1) & 0xFF,
|
|
((geom->segid+1)>>8) & 0xFF,
|
|
((geom->segid+1)>>16) & 0xFF,
|
|
0xFF
|
|
};
|
|
glColor4ubv(seg);
|
|
}
|
|
|
|
// random color
|
|
else {
|
|
float seg[4] = {
|
|
0.1f + 0.8f*mju_Halton(geom->segid+10, 2),
|
|
0.1f + 0.8f*mju_Halton(geom->segid+10, 3),
|
|
0.1f + 0.8f*mju_Halton(geom->segid+10, 5),
|
|
1
|
|
};
|
|
glColor4fv(seg);
|
|
}
|
|
}
|
|
|
|
// apply coordinate transformation, except for flex and skin which are global
|
|
glPushMatrix();
|
|
if (geom->type != mjGEOM_FLEX && geom->type != mjGEOM_SKIN) {
|
|
glTranslatef(geom->pos[0], geom->pos[1], geom->pos[2]);
|
|
glMultMatrixf(mat);
|
|
}
|
|
|
|
// render geom
|
|
switch (geom->type) {
|
|
case mjGEOM_PLANE: // plane
|
|
if (behind) {
|
|
glGetIntegerv(GL_CULL_FACE_MODE, &whichface);
|
|
if (whichface == GL_BACK) {
|
|
glCullFace(GL_FRONT);
|
|
}
|
|
}
|
|
|
|
// use plane-specific displaylist if available
|
|
if (geom->dataid >= 0) {
|
|
glCallList(con->basePlane + geom->dataid+1);
|
|
}
|
|
|
|
// otherwise default list
|
|
else {
|
|
glScalef(size[0], size[1], 1.0f);
|
|
glCallList(con->basePlane);
|
|
}
|
|
|
|
if (behind && whichface == GL_BACK) {
|
|
glCullFace(GL_BACK);
|
|
}
|
|
break;
|
|
|
|
case mjGEOM_HFIELD: // height field
|
|
if (geom->dataid >= 0) {
|
|
glCallList(con->baseHField + geom->dataid);
|
|
}
|
|
break;
|
|
|
|
case mjGEOM_SPHERE: // sphere
|
|
glScalef(size[0], size[1], size[2]);
|
|
glCallList(con->baseBuiltin + mjrSPHERE);
|
|
break;
|
|
|
|
case mjGEOM_CAPSULE: // capsule
|
|
av = 0.5*(size[0]+size[1]);
|
|
glScalef(size[0], size[1], size[2]);
|
|
glCallList(con->baseBuiltin + mjrCYLINDEROPEN);
|
|
glScalef(1, 1, av/size[2]);
|
|
glTranslatef(0, 0, (size[2]-av)/av);
|
|
glCallList(con->baseBuiltin + mjrSPHERETOP);
|
|
glTranslatef(0, 0, 2*(av-size[2])/av);
|
|
glCallList(con->baseBuiltin + mjrSPHEREBOTTOM);
|
|
break;
|
|
|
|
case mjGEOM_ELLIPSOID: // ellipsoid
|
|
glScalef(size[0], size[1], size[2]);
|
|
glCallList(con->baseBuiltin + mjrSPHERE);
|
|
break;
|
|
|
|
case mjGEOM_CYLINDER: // cylinder
|
|
glScalef(size[0], size[1], size[2]);
|
|
glCallList(con->baseBuiltin + mjrCYLINDER);
|
|
break;
|
|
|
|
case mjGEOM_BOX: // box
|
|
glScalef(size[0], size[1], size[2]);
|
|
glCallList(con->baseBuiltin + mjrBOX);
|
|
break;
|
|
|
|
case mjGEOM_MESH: // mesh
|
|
case mjGEOM_SDF:
|
|
if (geom->dataid >= 0) {
|
|
glCallList(con->baseMesh + geom->dataid);
|
|
}
|
|
break;
|
|
|
|
case mjGEOM_ARROW: // arrow
|
|
glScalef(size[0], size[1], size[2]/6.0f);
|
|
glTranslatef(0, 0, 1);
|
|
glCallList(con->baseBuiltin + mjrCYLINDER);
|
|
glTranslatef(0, 0, 1);
|
|
glScalef(1.75f, 1.75f, 1);
|
|
glCallList(con->baseBuiltin + mjrCONE);
|
|
break;
|
|
|
|
case mjGEOM_ARROW1: // arrow without wedges
|
|
glScalef(size[0], size[1], size[2]/6.0f);
|
|
glTranslatef(0, 0, 1);
|
|
glCallList(con->baseBuiltin + mjrCYLINDER);
|
|
glTranslatef(0, 0, 1);
|
|
glCallList(con->baseBuiltin + mjrCONE);
|
|
break;
|
|
|
|
case mjGEOM_ARROW2: // arrow in both directions
|
|
glScalef(size[0], size[1], size[2]/3.0f);
|
|
glCallList(con->baseBuiltin + mjrCYLINDEROPEN);
|
|
glScalef(1.75f, 1.75f, 0.5f);
|
|
glTranslatef(0, 0, -2);
|
|
glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
|
|
glCallList(con->baseBuiltin + mjrCONE);
|
|
glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
|
|
glTranslatef(0, 0, 4);
|
|
glCallList(con->baseBuiltin + mjrCONE);
|
|
break;
|
|
|
|
case mjGEOM_LINE: // line
|
|
glLineWidth(size[0]*con->lineWidth);
|
|
lighting = glIsEnabled(GL_LIGHTING);
|
|
glDisable(GL_LIGHTING);
|
|
glBegin(GL_LINES);
|
|
glVertex3f(0, 0, 0);
|
|
glVertex3f(0, 0, size[2]);
|
|
glEnd();
|
|
glLineWidth(con->lineWidth);
|
|
if (lighting) {
|
|
glEnable(GL_LIGHTING);
|
|
}
|
|
break;
|
|
|
|
case mjGEOM_LINEBOX: // box with line edges
|
|
glLineWidth(1.5*con->lineWidth);
|
|
lighting = glIsEnabled(GL_LIGHTING);
|
|
glDisable(GL_LIGHTING);
|
|
// bottom face
|
|
glBegin(GL_LINE_LOOP);
|
|
glVertex3f(-size[0], -size[1], -size[2]);
|
|
glVertex3f( size[0], -size[1], -size[2]);
|
|
glVertex3f( size[0], size[1], -size[2]);
|
|
glVertex3f(-size[0], size[1], -size[2]);
|
|
glEnd();
|
|
// top face
|
|
glBegin(GL_LINE_LOOP);
|
|
glVertex3f(-size[0], -size[1], size[2]);
|
|
glVertex3f( size[0], -size[1], size[2]);
|
|
glVertex3f( size[0], size[1], size[2]);
|
|
glVertex3f(-size[0], size[1], size[2]);
|
|
glEnd();
|
|
// vertical edges
|
|
glBegin(GL_LINES);
|
|
glVertex3f(-size[0], -size[1], -size[2]);
|
|
glVertex3f(-size[0], -size[1], size[2]);
|
|
glVertex3f( size[0], -size[1], -size[2]);
|
|
glVertex3f( size[0], -size[1], size[2]);
|
|
glVertex3f( size[0], size[1], -size[2]);
|
|
glVertex3f( size[0], size[1], size[2]);
|
|
glVertex3f(-size[0], size[1], -size[2]);
|
|
glVertex3f(-size[0], size[1], size[2]);
|
|
glEnd();
|
|
glLineWidth(con->lineWidth);
|
|
if (lighting) {
|
|
glEnable(GL_LIGHTING);
|
|
}
|
|
break;
|
|
|
|
case mjGEOM_TRIANGLE: // triangle
|
|
glDisable(GL_CULL_FACE);
|
|
glBegin(GL_TRIANGLES);
|
|
glVertex3f(0, 0, 0);
|
|
glVertex3f(size[0], 0, 0);
|
|
glVertex3f(0, size[1], 0);
|
|
glEnd();
|
|
if (scn->flags[mjRND_CULL_FACE]) {
|
|
glEnable(GL_CULL_FACE);
|
|
}
|
|
break;
|
|
|
|
case mjGEOM_FLEX: // flex
|
|
{
|
|
// no texture for vertices and edges
|
|
GLboolean texture_is_enabled = glIsEnabled(GL_TEXTURE_2D);
|
|
if (texture_is_enabled == GL_TRUE) {
|
|
glDisable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
// vertex spheres
|
|
if (size[0]>0 && scn->flexvertopt &&
|
|
!(scn->flexskinopt && scn->flexfaceused[geom->objid])) {
|
|
for (int v=scn->flexvertadr[geom->objid];
|
|
v<scn->flexvertadr[geom->objid]+scn->flexvertnum[geom->objid]; v++) {
|
|
glPushMatrix();
|
|
glTranslatef(scn->flexvert[3*v], scn->flexvert[3*v+1], scn->flexvert[3*v+2]);
|
|
glScalef(size[0], size[0], size[0]);
|
|
glCallList(con->baseBuiltin + mjrSPHERE);
|
|
glPopMatrix();
|
|
}
|
|
}
|
|
|
|
// edge cylinders
|
|
if (size[0]>0 && scn->flexedgeopt &&
|
|
!(scn->flexskinopt && scn->flexfaceused[geom->objid])) {
|
|
for (int e=scn->flexedgeadr[geom->objid];
|
|
e<scn->flexedgeadr[geom->objid]+scn->flexedgenum[geom->objid]; e++) {
|
|
// get vertices for this edge
|
|
float* v1 = scn->flexvert + 3*(scn->flexvertadr[geom->objid]+scn->flexedge[2*e]);
|
|
float* v2 = scn->flexvert + 3*(scn->flexvertadr[geom->objid]+scn->flexedge[2*e+1]);
|
|
|
|
// compute legth and rotation matrix
|
|
mjtNum vec[3] = {v2[0]-v1[0], v2[1]-v1[1], v2[2]-v1[2]};
|
|
mjtNum len = mju_normalize3(vec);
|
|
mjtNum edgequat[4], edgemat[9];
|
|
mju_quatZ2Vec(edgequat, vec);
|
|
mju_negQuat(edgequat, edgequat);
|
|
mju_quat2Mat(edgemat, edgequat);
|
|
mat[0] = (float)edgemat[0];
|
|
mat[1] = (float)edgemat[1];
|
|
mat[2] = (float)edgemat[2];
|
|
mat[4] = (float)edgemat[3];
|
|
mat[5] = (float)edgemat[4];
|
|
mat[6] = (float)edgemat[5];
|
|
mat[8] = (float)edgemat[6];
|
|
mat[9] = (float)edgemat[7];
|
|
mat[10] = (float)edgemat[8];
|
|
|
|
// transform and render
|
|
glPushMatrix();
|
|
glTranslatef((v1[0]+v2[0])*0.5f, (v1[1]+v2[1])*0.5f, (v1[2]+v2[2])*0.5f);
|
|
glMultMatrixf(mat);
|
|
glScalef(size[0], size[0], (float)(len*0.5));
|
|
glCallList(con->baseBuiltin + mjrCYLINDEROPEN);
|
|
glPopMatrix();
|
|
}
|
|
}
|
|
|
|
// restore texture for faces
|
|
if (texture_is_enabled == GL_TRUE) {
|
|
glEnable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
// face triangles
|
|
if (scn->flexfaceused[geom->objid]) {
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glEnableClientState(GL_NORMAL_ARRAY);
|
|
glVertexPointer(3, GL_FLOAT, 0, scn->flexface + 9*scn->flexfaceadr[geom->objid]);
|
|
glNormalPointer(GL_FLOAT, 0, scn->flexnormal + 9*scn->flexfaceadr[geom->objid]);
|
|
if (geom->texcoord && geom->matid>=0) {
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glTexCoordPointer(2, GL_FLOAT, 0, scn->flextexcoord + 6*scn->flexfaceadr[geom->objid]);
|
|
}
|
|
glDrawArrays(GL_TRIANGLES, 0, 3*scn->flexfaceused[geom->objid]);
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
glDisableClientState(GL_NORMAL_ARRAY);
|
|
if (geom->texcoord && geom->matid>=0) {
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case mjGEOM_SKIN: // skin
|
|
// vertex positions
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glBindBuffer(GL_ARRAY_BUFFER, con->skinvertVBO[geom->objid]);
|
|
glVertexPointer(3, GL_FLOAT, 0, NULL);
|
|
|
|
// vertex normals
|
|
glEnableClientState(GL_NORMAL_ARRAY);
|
|
glBindBuffer(GL_ARRAY_BUFFER, con->skinnormalVBO[geom->objid]);
|
|
glNormalPointer(GL_FLOAT, 0, NULL);
|
|
|
|
// vertex texture coordinates
|
|
if (con->skintexcoordVBO[geom->objid]) {
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glBindBuffer(GL_ARRAY_BUFFER, con->skintexcoordVBO[geom->objid]);
|
|
glTexCoordPointer(2, GL_FLOAT, 0, NULL);
|
|
} else {
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
}
|
|
|
|
// triangle face indices
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, con->skinfaceVBO[geom->objid]);
|
|
|
|
// draw
|
|
glDrawElements(GL_TRIANGLES, 3*scn->skinfacenum[geom->objid], GL_UNSIGNED_INT, NULL);
|
|
|
|
// restore
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
glDisableClientState(GL_NORMAL_ARRAY);
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
break;
|
|
}
|
|
|
|
// undo coordinate transformation
|
|
glPopMatrix();
|
|
|
|
// disable texture if enabled
|
|
if (geom->matid >= 0 &&
|
|
(mode == mjrRND_NORMAL || mode == mjrRND_SHADOWMAP)) {
|
|
settexture(mjtexREGULAR, 0, con, geom);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void renderGeomReflection(int id, float reflectance, float headpos[3],
|
|
mjvScene* scn, const mjrContext* con) {
|
|
float old_rgb[3];
|
|
|
|
// save rgb, modulate rgb by this->reflectance
|
|
for (int k=0; k < 3; k++) {
|
|
old_rgb[k] = scn->geoms[id].rgba[k];
|
|
scn->geoms[id].rgba[k] *= reflectance;
|
|
}
|
|
|
|
// render
|
|
renderGeom(scn->geoms+id, mjrRND_NORMAL, headpos, scn, con);
|
|
|
|
// restore rgb
|
|
for (int k=0; k < 3; k++) {
|
|
scn->geoms[id].rgba[k] = old_rgb[k];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//----------------------------- high-level 3D rendering --------------------------------------------
|
|
|
|
// init, with or without special effects
|
|
static void initGL3(const mjvScene* scn, const mjrContext* con) {
|
|
float rgbaWhite[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
|
|
|
// special effects
|
|
glEnable(GL_MULTISAMPLE);
|
|
glEnable(GL_LIGHTING);
|
|
if (scn->flags[mjRND_FOG]) {
|
|
glEnable(GL_FOG);
|
|
} else {
|
|
glDisable(GL_FOG);
|
|
}
|
|
|
|
// common options
|
|
glDisable(GL_BLEND);
|
|
glEnable(GL_NORMALIZE);
|
|
if (mjGLAD_GL_ARB_clip_control) {
|
|
glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
|
|
}
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDepthMask(GL_TRUE);
|
|
if (scn->flags[mjRND_CULL_FACE]) {
|
|
glEnable(GL_CULL_FACE);
|
|
} else {
|
|
glDisable(GL_CULL_FACE);
|
|
}
|
|
glShadeModel(GL_SMOOTH);
|
|
glDepthFunc(GL_GEQUAL);
|
|
glDepthRange(0, 1);
|
|
glAlphaFunc(GL_GEQUAL, 0.99f);
|
|
glClearColor(0, 0, 0, 0);
|
|
glClearDepth(0);
|
|
glClearStencil(0);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
|
|
// polygon mode
|
|
glPolygonMode(GL_FRONT_AND_BACK, scn->flags[mjRND_WIREFRAME] ? GL_LINE : GL_FILL);
|
|
glLineWidth(con->lineWidth);
|
|
glFrontFace(GL_CCW);
|
|
|
|
// fixed material properties; the rest are set by glColor (track material)
|
|
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, rgbaWhite);
|
|
glEnable(GL_COLOR_MATERIAL);
|
|
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
|
|
}
|
|
|
|
|
|
|
|
// init lights
|
|
static void initLights(mjvScene* scn) {
|
|
// create some ambient light if no ligths are present
|
|
float global = scn->nlight ? 0 : 0.3f;
|
|
float rgba_global[4] = {global, global, global, 1};
|
|
|
|
// init light model
|
|
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, rgba_global);
|
|
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0);
|
|
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);
|
|
|
|
// set light properties
|
|
for (int i=0; i < scn->nlight; i++) {
|
|
// colors
|
|
glLightfv(GL_LIGHT0+i, GL_AMBIENT, scn->lights[i].ambient);
|
|
glLightfv(GL_LIGHT0+i, GL_DIFFUSE, scn->lights[i].diffuse);
|
|
glLightfv(GL_LIGHT0+i, GL_SPECULAR, scn->lights[i].specular);
|
|
|
|
// parameters for directional light
|
|
if (scn->lights[i].type == mjLIGHT_DIRECTIONAL) {
|
|
glLightf(GL_LIGHT0+i, GL_SPOT_EXPONENT, 0);
|
|
glLightf(GL_LIGHT0+i, GL_SPOT_CUTOFF, 180);
|
|
glLightf(GL_LIGHT0+i, GL_CONSTANT_ATTENUATION, 1);
|
|
glLightf(GL_LIGHT0+i, GL_LINEAR_ATTENUATION, 0);
|
|
glLightf(GL_LIGHT0+i, GL_QUADRATIC_ATTENUATION, 0);
|
|
}
|
|
|
|
// parameters for spot light
|
|
else if (scn->lights[i].type == mjLIGHT_SPOT) {
|
|
glLightf(GL_LIGHT0+i, GL_SPOT_EXPONENT, scn->lights[i].exponent);
|
|
glLightf(GL_LIGHT0+i, GL_SPOT_CUTOFF, scn->lights[i].cutoff);
|
|
glLightf(GL_LIGHT0+i, GL_CONSTANT_ATTENUATION, scn->lights[i].attenuation[0]);
|
|
glLightf(GL_LIGHT0+i, GL_LINEAR_ATTENUATION, scn->lights[i].attenuation[1]);
|
|
glLightf(GL_LIGHT0+i, GL_QUADRATIC_ATTENUATION, scn->lights[i].attenuation[2]);
|
|
}
|
|
|
|
else {
|
|
mju_error("Unsupported light type: %d", scn->lights[i].type);
|
|
}
|
|
}
|
|
|
|
// disable all lights (enable selectively in render)
|
|
for (int i=0; i < scn->nlight; i++) {
|
|
glDisable(GL_LIGHT0+i);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// set projection and modelview
|
|
static void setView(int view, mjrRect viewport, const mjvScene* scn, const mjrContext* con,
|
|
float camProject[16], float camView[16]) {
|
|
mjvGLCamera cam;
|
|
|
|
// copy specified camera for stereo, average for mono (view = -1)
|
|
if (view >= 0) {
|
|
cam = scn->camera[view];
|
|
} else {
|
|
cam = mjv_averageCamera(scn->camera, scn->camera+1);
|
|
}
|
|
|
|
// compute frustum halfwidth so as to match viewport aspect ratio
|
|
float halfwidth = cam.frustum_width ? cam.frustum_width
|
|
: 0.5f * (float)viewport.width / (float)viewport.height *
|
|
(cam.frustum_top - cam.frustum_bottom);
|
|
|
|
// prepare projection
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
if (mjGLAD_GL_ARB_clip_control) {
|
|
// reverse Z rendering mapping [znear, zfar] -> [1, 0] (ndc)
|
|
glTranslatef(0.0f, 0.0f, 0.5f);
|
|
glScalef(1.0f, 1.0f, -0.5f);
|
|
} else {
|
|
// reverse Z rendering mapping without shift [znear, zfar] -> [1, -1] (ndc)
|
|
glScalef(1.0f, 1.0f, -1.0f);
|
|
}
|
|
|
|
// set projection, orthographic or perspective
|
|
if (cam.orthographic) {
|
|
glOrtho(cam.frustum_center - halfwidth,
|
|
cam.frustum_center + halfwidth,
|
|
cam.frustum_bottom,
|
|
cam.frustum_top,
|
|
cam.frustum_near,
|
|
cam.frustum_far);
|
|
} else {
|
|
glFrustum(cam.frustum_center - halfwidth,
|
|
cam.frustum_center + halfwidth,
|
|
cam.frustum_bottom,
|
|
cam.frustum_top,
|
|
cam.frustum_near,
|
|
cam.frustum_far);
|
|
}
|
|
|
|
// save projection matrix if requested
|
|
if (camProject) {
|
|
glGetFloatv(GL_PROJECTION_MATRIX, camProject);
|
|
}
|
|
|
|
// set modelview
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
mjr_lookAt(cam.pos, cam.forward, cam.up);
|
|
if (scn->enabletransform) {
|
|
mjr_transform(scn->translate, scn->rotate, scn->scale);
|
|
}
|
|
|
|
// save modelview matrix if requested
|
|
if (camView) {
|
|
glGetFloatv(GL_MODELVIEW_MATRIX, camView);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// comparison function for geom sorting
|
|
static inline int geomcmp(int* i, int* j, void* context) {
|
|
mjvGeom* geom = (mjvGeom*) context;
|
|
float d1 = geom[*i].camdist;
|
|
float d2 = geom[*j].camdist;
|
|
|
|
if (d1 < d2) {
|
|
return -1;
|
|
} else if (d1 == d2) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// define geomSort function for sorting geoms
|
|
mjSORT(geomSort, int, geomcmp)
|
|
|
|
|
|
|
|
// adjust light n position and direction
|
|
static void adjustLight(const mjvLight* thislight, int n) {
|
|
float temp[4];
|
|
|
|
// set position and direction according to type
|
|
if (thislight->type == mjLIGHT_DIRECTIONAL) {
|
|
mjr_setf4(temp, -thislight->dir[0], -thislight->dir[1], -thislight->dir[2], 0);
|
|
glLightfv(GL_LIGHT0+n, GL_POSITION, temp);
|
|
} else if (thislight->type == mjLIGHT_SPOT) {
|
|
mjr_setf4(temp, thislight->dir[0], thislight->dir[1], thislight->dir[2], 0);
|
|
glLightfv(GL_LIGHT0+n, GL_SPOT_DIRECTION, temp);
|
|
mjr_setf4(temp, thislight->pos[0], thislight->pos[1], thislight->pos[2], 1);
|
|
glLightfv(GL_LIGHT0+n, GL_POSITION, temp);
|
|
} else {
|
|
mju_error("Unsupported light type: %d", thislight->type);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// render
|
|
void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con) {
|
|
int stereo, nt, ngeom = scn->ngeom, nlight = mjMIN(mjMAXLIGHT, scn->nlight);
|
|
unsigned int drawbuffer;
|
|
mjvGLCamera cam;
|
|
mjtNum hpos[3];
|
|
float temp[4], headpos[3], skyboxdst;
|
|
float camProject[16], camView[16], lightProject[16], lightView[16];
|
|
double clipplane[4];
|
|
float biasMatrix[16] = {
|
|
0.5f, 0.0f, 0.0f, 0.0f,
|
|
0.0f, 0.5f, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
0.5f, 0.5f, 0.0f, 1.0f
|
|
};
|
|
if (!mjGLAD_GL_ARB_clip_control) {
|
|
// account for conversion from ndc to window coordinates
|
|
biasMatrix[2*4+2] = 0.5;
|
|
biasMatrix[3*4+2] = 0.5;
|
|
}
|
|
|
|
float tempMatrix[16], textureMatrix[16];
|
|
mjvGeom *thisgeom, tempgeom;
|
|
mjvLight *thislight;
|
|
|
|
// empty viewport: nothing to do
|
|
if (viewport.width <= 0 || viewport.height <= 0) {
|
|
return;
|
|
}
|
|
|
|
// average cameras
|
|
cam = mjv_averageCamera(scn->camera, scn->camera+1);
|
|
|
|
// check znear
|
|
if (cam.frustum_near < mjMINVAL) {
|
|
// geoms: error
|
|
if (scn->ngeom) {
|
|
mju_error("mjvScene frustum_near too small in mjr_render");
|
|
}
|
|
|
|
// no geoms: return silently
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// upload dynamic skin data to GPU
|
|
for (int i=0; i < scn->nskin; i++) {
|
|
// upload positions to VBO
|
|
glBindBuffer(GL_ARRAY_BUFFER, con->skinvertVBO[i]);
|
|
glBufferData(GL_ARRAY_BUFFER,
|
|
3*scn->skinvertnum[i]*sizeof(float),
|
|
scn->skinvert + 3*scn->skinvertadr[i],
|
|
GL_STREAM_DRAW);
|
|
|
|
// upload normals to VBO
|
|
glBindBuffer(GL_ARRAY_BUFFER, con->skinnormalVBO[i]);
|
|
glBufferData(GL_ARRAY_BUFFER,
|
|
3*scn->skinvertnum[i]*sizeof(float),
|
|
scn->skinnormal + 3*scn->skinvertadr[i],
|
|
GL_STREAM_DRAW);
|
|
}
|
|
|
|
// determine drawbuffer; may be changed by stereo later
|
|
if (con->currentBuffer == mjFB_WINDOW) {
|
|
drawbuffer = (con->windowDoublebuffer ? GL_BACK : GL_FRONT);
|
|
} else {
|
|
drawbuffer = GL_COLOR_ATTACHMENT0;
|
|
}
|
|
|
|
// init lights
|
|
initLights(scn);
|
|
|
|
// compute head position in model space
|
|
mjv_cameraInModel(hpos, NULL, NULL, scn);
|
|
mju_n2f(headpos, hpos, 3);
|
|
|
|
// make list of transparent geoms
|
|
nt = 0;
|
|
for (int i=0; i < ngeom; i++) {
|
|
// get geom pointer
|
|
thisgeom = scn->geoms + i;
|
|
|
|
if (thisgeom->rgba[3] < 0.995 || (thisgeom->type == mjGEOM_PLANE &&
|
|
isBehind(headpos, thisgeom->pos, thisgeom->mat))) {
|
|
// include index in list
|
|
scn->geomorder[nt++] = i;
|
|
thisgeom->transparent = 1;
|
|
|
|
// compute distance to camera
|
|
thisgeom->camdist = sqrtf((thisgeom->pos[0]-headpos[0])*(thisgeom->pos[0]-headpos[0]) +
|
|
(thisgeom->pos[1]-headpos[1])*(thisgeom->pos[1]-headpos[1]) +
|
|
(thisgeom->pos[2]-headpos[2])*(thisgeom->pos[2]-headpos[2]));
|
|
|
|
// correct for rbound
|
|
thisgeom->camdist -= mjv_rbound(thisgeom);
|
|
|
|
// plane always far away
|
|
if (thisgeom->type == mjGEOM_PLANE) {
|
|
thisgeom->camdist = 1E+10;
|
|
}
|
|
} else {
|
|
thisgeom->transparent = 0;
|
|
}
|
|
}
|
|
|
|
// sort transparent geoms according to distance to camera
|
|
if (nt > 1) {
|
|
int *buf = (int*) mju_malloc(nt * sizeof(int));
|
|
geomSort(scn->geomorder, buf, nt, scn->geoms);
|
|
mju_free(buf);
|
|
}
|
|
|
|
// allow only one reflective geom
|
|
int j = 0;
|
|
for (int i=0; i < ngeom; i++) {
|
|
if (j) {
|
|
scn->geoms[i].reflectance = 0;
|
|
} else if (isReflective(scn->geoms + i)) {
|
|
j = 1;
|
|
}
|
|
}
|
|
|
|
// init OpenGL
|
|
initGL3(scn, con);
|
|
|
|
// set full viewport
|
|
glViewport(viewport.left, viewport.bottom, viewport.width, viewport.height);
|
|
|
|
// clear, with scissor
|
|
glScissor(viewport.left, viewport.bottom, viewport.width, viewport.height);
|
|
glEnable(GL_SCISSOR_TEST);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
glDisable(GL_SCISSOR_TEST);
|
|
|
|
// determine stereo; quadbuffered reverts to sidebyside if hardware not available
|
|
stereo = scn->stereo;
|
|
if (stereo == mjSTEREO_QUADBUFFERED &&
|
|
(con->currentBuffer != mjFB_WINDOW || !con->windowStereo)) {
|
|
stereo = mjSTEREO_SIDEBYSIDE;
|
|
}
|
|
|
|
// SIDEBYSIDE: reduce viewport
|
|
if (stereo == mjSTEREO_SIDEBYSIDE) {
|
|
viewport.width /= 2;
|
|
}
|
|
|
|
// render with stereo
|
|
for (int view = (stereo ? 0 : -1); view < (stereo ? 2 : 0); view++) {
|
|
// change drawbuffer for QUADBUFFERED stereo
|
|
if (stereo == mjSTEREO_QUADBUFFERED) {
|
|
if (con->windowDoublebuffer) {
|
|
drawbuffer = (view ? GL_BACK_RIGHT : GL_BACK_LEFT);
|
|
} else {
|
|
drawbuffer = (view ? GL_FRONT_RIGHT : GL_FRONT_LEFT);
|
|
}
|
|
glDrawBuffer(drawbuffer);
|
|
|
|
// clear depth buffer for second view (since it is shared)
|
|
if (view) {
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
}
|
|
}
|
|
|
|
// change viewport for SIDEBYSIDE stereo
|
|
else if (stereo == mjSTEREO_SIDEBYSIDE) {
|
|
// move viewport to the right for view 1
|
|
if (view) {
|
|
viewport.left += viewport.width;
|
|
}
|
|
|
|
// set reduced/moved viewport
|
|
glViewport(viewport.left, viewport.bottom, viewport.width, viewport.height);
|
|
}
|
|
|
|
// set projection and modelview
|
|
setView(view, viewport, scn, con, camProject, camView);
|
|
|
|
//---------------------------------- segmentation rendering
|
|
|
|
if (scn->flags[mjRND_SEGMENT]) {
|
|
// constant color rendering
|
|
glShadeModel(GL_FLAT);
|
|
glDisable(GL_LIGHTING);
|
|
glDisable(GL_COLOR_MATERIAL);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
glDisable(GL_FOG);
|
|
glDisable(GL_MULTISAMPLE);
|
|
|
|
// render all geoms
|
|
for (int i=0; i < ngeom; i++) {
|
|
if (scn->geoms[i].segid >= 0) {
|
|
renderGeom(scn->geoms+i,
|
|
scn->flags[mjRND_IDCOLOR] ? mjrRND_IDCOLOR : mjrRND_SEGMENT,
|
|
headpos, scn, con);
|
|
}
|
|
}
|
|
|
|
// skip the remaining passes
|
|
continue;
|
|
}
|
|
|
|
//---------------------------------- reflection rendering
|
|
|
|
// plane and box reflection rendering
|
|
if (scn->flags[mjRND_REFLECTION]) {
|
|
for (int i=0; i < ngeom; i++) {
|
|
// get geom pointer
|
|
thisgeom = scn->geoms + i;
|
|
|
|
if (isReflective(thisgeom)) {
|
|
// if box, replace with temporary plane matching Z+ box side
|
|
if (thisgeom->type == mjGEOM_BOX) {
|
|
// copy and convert to plane
|
|
tempgeom = *thisgeom;
|
|
tempgeom.type = mjGEOM_PLANE;
|
|
|
|
// offset position to Z+ face side
|
|
tempgeom.pos[0] += tempgeom.size[2]*tempgeom.mat[2];
|
|
tempgeom.pos[1] += tempgeom.size[2]*tempgeom.mat[5];
|
|
tempgeom.pos[2] += tempgeom.size[2]*tempgeom.mat[8];
|
|
|
|
// redirect pointer
|
|
thisgeom = &tempgeom;
|
|
}
|
|
|
|
// camera behind plane or box side: nothing to show
|
|
if (isBehind(headpos, thisgeom->pos, thisgeom->mat)) {
|
|
continue;
|
|
}
|
|
|
|
// prepare to render plane in stencil buffer
|
|
glDisable(GL_DEPTH_TEST);
|
|
glColorMask(0, 0, 0, 0);
|
|
glEnable(GL_STENCIL_TEST);
|
|
glClear(GL_STENCIL_BUFFER_BIT);
|
|
glStencilFunc(GL_ALWAYS, 1, 0xFF);
|
|
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
|
|
|
|
// render thisgeom in stencil buffer, always fill
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
renderGeom(thisgeom, mjrRND_NORMAL, headpos, scn, con);
|
|
glPolygonMode(GL_FRONT_AND_BACK, scn->flags[mjRND_WIREFRAME] ? GL_LINE : GL_FILL);
|
|
|
|
// prepare to render scene where stencil==1
|
|
glEnable(GL_DEPTH_TEST);
|
|
glColorMask(1, 1, 1, 1);
|
|
glStencilFunc(GL_EQUAL, 1, 0xFF);
|
|
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
|
|
|
// set clipplane
|
|
glEnable(GL_CLIP_PLANE0);
|
|
clipplane[0] = -thisgeom->mat[2];
|
|
clipplane[1] = -thisgeom->mat[5];
|
|
clipplane[2] = -thisgeom->mat[8];
|
|
clipplane[3] =
|
|
thisgeom->pos[0]*thisgeom->mat[2] +
|
|
thisgeom->pos[1]*thisgeom->mat[5] +
|
|
thisgeom->pos[2]*thisgeom->mat[8];
|
|
glClipPlane(GL_CLIP_PLANE0, clipplane);
|
|
|
|
// set reflection matrix
|
|
glFrontFace(GL_CW);
|
|
glPushMatrix();
|
|
mjr_reflect(thisgeom->pos, thisgeom->mat);
|
|
|
|
// set light position and direction, enable
|
|
for (int j=0; j < nlight; j++) {
|
|
adjustLight(scn->lights+j, j);
|
|
glEnable(GL_LIGHT0+j);
|
|
}
|
|
|
|
// render reflected non-transparent geoms, except for thisgeom
|
|
for (int j=0; j < ngeom; j++) {
|
|
if (!scn->geoms[j].transparent && i != j) {
|
|
renderGeomReflection(j, thisgeom->reflectance, headpos, scn, con);
|
|
}
|
|
}
|
|
|
|
// render reflected transparent geoms, except for thisgeom
|
|
glDepthMask(GL_FALSE);
|
|
glEnable(GL_BLEND);
|
|
if (scn->flags[mjRND_ADDITIVE]) {
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
|
} else {
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
for (int j=0; j < nt; j++) {
|
|
if (i != scn->geomorder[j]) {
|
|
renderGeomReflection(scn->geomorder[j], thisgeom->reflectance, headpos, scn, con);
|
|
}
|
|
}
|
|
if (!scn->flags[mjRND_ADDITIVE]) {
|
|
for (int j=nt-1; j >= 0; j--) {
|
|
if (i != scn->geomorder[j]) {
|
|
renderGeomReflection(scn->geomorder[j], thisgeom->reflectance, headpos, scn, con);
|
|
}
|
|
}
|
|
}
|
|
glDepthMask(GL_TRUE);
|
|
glDisable(GL_BLEND);
|
|
|
|
// disable lights
|
|
for (int j=0; j < nlight; j++) {
|
|
glDisable(GL_LIGHT0+j);
|
|
}
|
|
|
|
// end reflection rendering
|
|
glDisable(GL_STENCIL_TEST);
|
|
glDisable(GL_CLIP_PLANE0);
|
|
glPopMatrix();
|
|
glFrontFace(GL_CCW);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------- regular rendering
|
|
|
|
// set light position and direction, enable non-shadow lights
|
|
for (int i=0; i < nlight; i++) {
|
|
// set light
|
|
thislight = scn->lights + i;
|
|
adjustLight(thislight, i);
|
|
|
|
// enable lights without shadows
|
|
if (!thislight->castshadow || !(scn->flags[mjRND_SHADOW] && con->shadowFBO)) {
|
|
glEnable(GL_LIGHT0+i);
|
|
}
|
|
}
|
|
|
|
// render reflecting geoms
|
|
// (only one allowed, more would result in weird transparency)
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_ONE, GL_ONE);
|
|
for (int i=0; i < ngeom; i++)
|
|
if (isReflective(scn->geoms+i)) {
|
|
renderGeom(scn->geoms+i, mjrRND_NORMAL, headpos, scn, con);
|
|
}
|
|
glDisable(GL_BLEND);
|
|
|
|
// render remaining opaque geoms
|
|
for (int i=0; i < ngeom; i++) {
|
|
if (!scn->geoms[i].transparent && !isReflective(scn->geoms+i)) {
|
|
renderGeom(scn->geoms+i, mjrRND_NORMAL, headpos, scn, con);
|
|
}
|
|
}
|
|
|
|
// disable lights
|
|
for (int i=0; i < nlight; i++) {
|
|
glDisable(GL_LIGHT0+i);
|
|
}
|
|
|
|
//------------------------------------ shadow rendering
|
|
|
|
// black fog, to avoid glow
|
|
float black[4] = {0, 0, 0, 0};
|
|
glFogfv(GL_FOG_COLOR, black);
|
|
|
|
// shadow map rendering
|
|
if (scn->flags[mjRND_SHADOW] && con->shadowFBO) {
|
|
for (int i=0; i < nlight; i++) {
|
|
// get pointer
|
|
thislight = scn->lights + i;
|
|
|
|
if (thislight->castshadow) {
|
|
// prepare up-direction
|
|
mjr_orthoVec(temp, thislight->dir);
|
|
|
|
// set projection: from light viewpoint
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
if (mjGLAD_GL_ARB_clip_control) {
|
|
// reverse Z rendering mapping [znear, zfar] -> [1, 0] (ndc)
|
|
glTranslatef(0.0f, 0.0f, 0.5f);
|
|
glScalef(1.0f, 1.0f, -0.5f);
|
|
}
|
|
else {
|
|
// reverse Z rendering mapping without shift [znear, zfar] -> [1, -1] (ndc)
|
|
glScalef(1.0f, 1.0f, -1.0f);
|
|
}
|
|
if (thislight->type == mjLIGHT_DIRECTIONAL) {
|
|
glOrtho(-con->shadowClip, con->shadowClip,
|
|
-con->shadowClip, con->shadowClip,
|
|
cam.frustum_near, cam.frustum_far);
|
|
} else if (thislight->type == mjLIGHT_SPOT) {
|
|
mjr_perspective(mju_min(2*thislight->cutoff*con->shadowScale, 160), 1,
|
|
cam.frustum_near, cam.frustum_far);
|
|
} else {
|
|
mju_error("Unsupported light type: %d", thislight->type);
|
|
}
|
|
glGetFloatv(GL_PROJECTION_MATRIX, lightProject);
|
|
|
|
// set modelview: from light viewpoint
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
mjr_lookAt(thislight->pos, thislight->dir, temp);
|
|
glGetFloatv(GL_MODELVIEW_MATRIX, lightView);
|
|
|
|
// adjust OpenGL settings for shadow rendering
|
|
glBindFramebuffer(GL_FRAMEBUFFER, con->shadowFBO);
|
|
glDrawBuffer(GL_NONE);
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
glViewport(
|
|
1, 1, con->shadowSize-2, con->shadowSize-2); // avoid infinite shadows from edges
|
|
glShadeModel(GL_FLAT);
|
|
glDisable(GL_LIGHTING);
|
|
glColorMask(0, 0, 0, 0);
|
|
int cull_face = glIsEnabled(GL_CULL_FACE);
|
|
glDisable(GL_CULL_FACE); // all faces cast shadows
|
|
glEnable(GL_POLYGON_OFFSET_FILL);
|
|
|
|
// The limited resolution of the shadow maps means multiple fragments
|
|
// sample the same texel. When light and camera directions differ on
|
|
// surfaces that should be lit this causes "shadow acne" because some
|
|
// fragments will be lit while adjacent fragments are not. To mitigate
|
|
// this artifact, an offset is applied to the depth values in the
|
|
// shadow map. The offset must be large enough to ensure consistent
|
|
// depth comparison occurs within the limited precision of the depth
|
|
// buffer. The offset is computed by glPolygonOffset using parameters
|
|
// that are chosen empirically. We need different values when clip
|
|
// control is on/off because this setting changes the depth precision.
|
|
float kOffsetFactor = -16.0f;
|
|
float kOffsetUnits = -512.0f;
|
|
if (mjGLAD_GL_ARB_clip_control) {
|
|
kOffsetFactor = -1.5f;
|
|
kOffsetUnits = -4.0f;
|
|
}
|
|
glPolygonOffset(kOffsetFactor, kOffsetUnits);
|
|
|
|
// render all geoms to depth texture
|
|
for (int j=0; j < ngeom; j++) {
|
|
renderGeom(scn->geoms+j, mjrRND_SHADOWCAST, headpos, scn, con);
|
|
}
|
|
|
|
// restore OpenGL settings
|
|
glBindFramebuffer(GL_FRAMEBUFFER,
|
|
con->currentBuffer == mjFB_WINDOW ? 0 : con->offFBO);
|
|
glDrawBuffer(drawbuffer);
|
|
glViewport(viewport.left, viewport.bottom, viewport.width, viewport.height);
|
|
if (cull_face) {
|
|
glEnable(GL_CULL_FACE);
|
|
}
|
|
glDisable(GL_POLYGON_OFFSET_FILL);
|
|
glShadeModel(GL_SMOOTH);
|
|
glEnable(GL_LIGHTING);
|
|
glColorMask(1, 1, 1, 1);
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadMatrixf(camProject);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadMatrixf(camView);
|
|
|
|
// compute camera-light mapping
|
|
mjr_mulMat44(tempMatrix, lightProject, lightView);
|
|
mjr_mulMat44(textureMatrix, biasMatrix, tempMatrix);
|
|
|
|
// enable texture machinery
|
|
settexture(mjtexSHADOW, 1, con, 0);
|
|
mjr_getrow4(temp, textureMatrix, 0);
|
|
glTexGenfv(GL_S, GL_EYE_PLANE, temp);
|
|
mjr_getrow4(temp, textureMatrix, 1);
|
|
glTexGenfv(GL_T, GL_EYE_PLANE, temp);
|
|
mjr_getrow4(temp, textureMatrix, 2);
|
|
glTexGenfv(GL_R, GL_EYE_PLANE, temp);
|
|
mjr_getrow4(temp, textureMatrix, 3);
|
|
glTexGenfv(GL_Q, GL_EYE_PLANE, temp);
|
|
|
|
// render with shadow light i
|
|
glEnable(GL_ALPHA_TEST);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_ONE, GL_ONE);
|
|
glEnable(GL_LIGHT0+i);
|
|
|
|
// only opaque geoms accept shadows
|
|
for (int j=0; j < ngeom; j++) {
|
|
if (!scn->geoms[j].transparent) {
|
|
renderGeom(scn->geoms+j, mjrRND_SHADOWMAP, headpos, scn, con);
|
|
}
|
|
}
|
|
|
|
glDisable(GL_LIGHT0+i);
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_ALPHA_TEST);
|
|
|
|
// disable texture machinery
|
|
settexture(mjtexSHADOW, 0, con, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// restore fog color
|
|
glFogfv(GL_FOG_COLOR, con->fogRGBA);
|
|
|
|
//------------------------------------ skybox rendering
|
|
|
|
if (scn->flags[mjRND_SKYBOX]) {
|
|
// skybox always filled
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
|
|
// find skybox texture
|
|
for (int i=0; i < con->ntexture; i++) {
|
|
if (con->textureType[i] == mjTEXTURE_SKYBOX) {
|
|
// save first skybox texture id in tempgeom
|
|
memset(&tempgeom, 0, sizeof(mjvGeom));
|
|
tempgeom.matid = mjMAXMATERIAL - 1;
|
|
|
|
// modify settings
|
|
glDisable(GL_LIGHTING);
|
|
glDisable(GL_COLOR_MATERIAL);
|
|
glCullFace(GL_FRONT);
|
|
|
|
// center at headpos, scale to avoid far clipping: just below 1/sqrt(2)
|
|
glPushMatrix();
|
|
glTranslatef(headpos[0], headpos[1], headpos[2]);
|
|
skyboxdst = cam.frustum_far*0.70f;
|
|
if (scn->enabletransform) {
|
|
skyboxdst /= scn->scale;
|
|
}
|
|
glScalef(skyboxdst, skyboxdst, skyboxdst);
|
|
|
|
// render cylinder, with skybox texture
|
|
settexture(mjtexSKYBOX, 1, con, &tempgeom);
|
|
glColor4f(1, 1, 1, 1);
|
|
glCallList(con->baseBuiltin + mjrCYLINDER);
|
|
settexture(mjtexSKYBOX, 0, con, &tempgeom);
|
|
|
|
// haze
|
|
if (scn->flags[mjRND_HAZE]) {
|
|
for (int j=0; j < ngeom; j++) {
|
|
if (scn->geoms[j].type == mjGEOM_PLANE &&
|
|
scn->geoms[j].size[0] == 0 &&
|
|
scn->geoms[j].size[1] == 0) {
|
|
// compute headpos elevation above plane
|
|
float* mat3 = scn->geoms[j].mat;
|
|
float elevation = ((headpos[0]-scn->geoms[j].pos[0])*mat3[2] +
|
|
(headpos[1]-scn->geoms[j].pos[1])*mat3[5] +
|
|
(headpos[2]-scn->geoms[j].pos[2])*mat3[8]) /
|
|
skyboxdst;
|
|
|
|
// below plane: no rendering
|
|
if (elevation < 0) {
|
|
break;
|
|
}
|
|
|
|
// rotate to plane
|
|
float mat4[16] = {
|
|
mat3[0], mat3[3], mat3[6], 0.0f,
|
|
mat3[1], mat3[4], mat3[7], 0.0f,
|
|
mat3[2], mat3[5], mat3[8], 0.0f,
|
|
0.0f, 0.0f, 0.0f, 1.0f
|
|
};
|
|
glMultMatrixf(mat4);
|
|
|
|
// translate and scale
|
|
glTranslatef(0, 0, -elevation);
|
|
glScalef(1, 1, elevation);
|
|
|
|
// render
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glCallList(con->baseBuiltin + mjrHAZE);
|
|
glDisable(GL_BLEND);
|
|
|
|
// only first infinite plane
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// recover settings
|
|
glPopMatrix();
|
|
glEnable(GL_LIGHTING);
|
|
glEnable(GL_COLOR_MATERIAL);
|
|
glCullFace(GL_BACK);
|
|
|
|
// render only first skybox
|
|
break;
|
|
}
|
|
}
|
|
|
|
// wireframe or filled depending on flag
|
|
glPolygonMode(GL_FRONT_AND_BACK, scn->flags[mjRND_WIREFRAME] ? GL_LINE : GL_FILL);
|
|
}
|
|
|
|
//------------------------------------ transparent regular rendering
|
|
|
|
// enable lights
|
|
for (int i=0; i < nlight; i++) {
|
|
glEnable(GL_LIGHT0+i);
|
|
}
|
|
|
|
// blend mode
|
|
glDepthMask(GL_FALSE);
|
|
glEnable(GL_BLEND);
|
|
if (scn->flags[mjRND_ADDITIVE]) {
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
|
} else {
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
|
|
// render transparent geoms: front to back
|
|
for (int i=0; i < nt; i++) {
|
|
renderGeom(scn->geoms+scn->geomorder[i], mjrRND_NORMAL, headpos, scn, con);
|
|
}
|
|
|
|
// render transparent geoms: back to front, if not additive
|
|
if (!scn->flags[mjRND_ADDITIVE]) {
|
|
for (int i=nt-1; i >= 0; i--) {
|
|
renderGeom(scn->geoms+scn->geomorder[i], mjrRND_NORMAL, headpos, scn, con);
|
|
}
|
|
}
|
|
|
|
// normal mode
|
|
glDepthMask(GL_TRUE);
|
|
glDisable(GL_BLEND);
|
|
|
|
// disable lights
|
|
for (int i=0; i < nlight; i++) {
|
|
glDisable(GL_LIGHT0+i);
|
|
}
|
|
|
|
//------------------------------------ label rendering
|
|
|
|
// render text labels if present
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_LIGHTING);
|
|
for (int i=0; i < ngeom; i++) {
|
|
thisgeom = scn->geoms + i;
|
|
|
|
if (thisgeom->label[0]) {
|
|
mjr_textActual(mjFONT_SHADOW, thisgeom->label, con,
|
|
thisgeom->pos[0], thisgeom->pos[1], thisgeom->pos[2], 1, 1, 1);
|
|
}
|
|
}
|
|
glEnable(GL_DEPTH_TEST);
|
|
glEnable(GL_LIGHTING);
|
|
}
|
|
|
|
// frame
|
|
if (scn->framewidth) {
|
|
// prepare
|
|
glClearColor(scn->framergb[0], scn->framergb[1], scn->framergb[2], 0);
|
|
glEnable(GL_SCISSOR_TEST);
|
|
|
|
// bottom edge
|
|
glScissor(viewport.left, viewport.bottom,
|
|
viewport.width, scn->framewidth);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
// top edge
|
|
glScissor(viewport.left, viewport.bottom+viewport.height-scn->framewidth,
|
|
viewport.width, scn->framewidth);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
// left edge
|
|
glScissor(viewport.left, viewport.bottom,
|
|
scn->framewidth, viewport.height);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
// right edge
|
|
glScissor(viewport.left+viewport.width-scn->framewidth, viewport.bottom,
|
|
scn->framewidth, viewport.height);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
// disable scissor
|
|
glDisable(GL_SCISSOR_TEST);
|
|
}
|
|
|
|
// restore currentBuffer
|
|
mjr_restoreBuffer(con);
|
|
}
|
|
|
|
|
|
|
|
// call glFinish
|
|
void mjr_finish(void) {
|
|
glFinish();
|
|
}
|
|
|
|
|
|
|
|
// call glGetError and return result
|
|
int mjr_getError(void) {
|
|
return (int)glGetError();
|
|
}
|